From 8dd57ffed2f1225cd1b576462045faccc255721b Mon Sep 17 00:00:00 2001 From: Alexander Skvortsov Date: Tue, 16 Mar 2021 23:04:08 -0400 Subject: [PATCH] Include task scheduler in core --- composer.json | 2 ++ src/Console/ConsoleServiceProvider.php | 32 +++++++++++++++++ src/Console/Schedule.php | 40 +++++++++++++++++++++ src/Extend/Console.php | 29 +++++++++++++++ src/Queue/QueueServiceProvider.php | 10 +++++- src/helpers.php | 18 +++++++--- tests/integration/extenders/ConsoleTest.php | 31 ++++++++++++++++ 7 files changed, 157 insertions(+), 5 deletions(-) create mode 100644 src/Console/Schedule.php diff --git a/composer.json b/composer.json index 799cbe2ba..68ae6b2b5 100644 --- a/composer.json +++ b/composer.json @@ -25,10 +25,12 @@ "components/font-awesome": "^5.14.0", "dflydev/fig-cookies": "^3.0.0", "doctrine/dbal": "^2.7", + "dragonmantank/cron-expression": "^3.1.0", "franzl/whoops-middleware": "^2.0.0", "illuminate/bus": "^8.0", "illuminate/cache": "^8.0", "illuminate/config": "^8.0", + "illuminate/console": "^8.0", "illuminate/container": "^8.0", "illuminate/contracts": "^8.0", "illuminate/database": "^8.0", diff --git a/src/Console/ConsoleServiceProvider.php b/src/Console/ConsoleServiceProvider.php index 3b00caae9..7887fde72 100644 --- a/src/Console/ConsoleServiceProvider.php +++ b/src/Console/ConsoleServiceProvider.php @@ -14,6 +14,9 @@ use Flarum\Database\Console\ResetCommand; use Flarum\Foundation\AbstractServiceProvider; use Flarum\Foundation\Console\CacheClearCommand; use Flarum\Foundation\Console\InfoCommand; +use Illuminate\Console\Scheduling\Schedule as LaravelSchedule; +use Illuminate\Console\Scheduling\ScheduleListCommand; +use Illuminate\Console\Scheduling\ScheduleRunCommand; class ConsoleServiceProvider extends AbstractServiceProvider { @@ -22,13 +25,42 @@ class ConsoleServiceProvider extends AbstractServiceProvider */ public function register() { + // Used by Laravel to proxy artisan commands to its binary. + // Flarum uses a similar binary, but it's called flarum. + if (! defined('ARTISAN_BINARY')) { + define('ARTISAN_BINARY', 'flarum'); + } + + $this->container->singleton(LaravelSchedule::class, function () { + return $this->container->make(Schedule::class); + }); + $this->container->singleton('flarum.console.commands', function () { return [ CacheClearCommand::class, InfoCommand::class, MigrateCommand::class, ResetCommand::class, + ScheduleListCommand::class, + ScheduleRunCommand::class ]; }); + + $this->container->singleton('flarum.console.scheduled', function () { + return []; + }); + } + + /** + * {@inheritDoc} + */ + public function boot() + { + $schedule = $this->container->make(LaravelSchedule::class); + + foreach ($this->container->make('flarum.console.scheduled') as $scheduled) { + $event = $schedule->command($scheduled['command'], $scheduled['args']); + $scheduled['callback']($event); + } } } diff --git a/src/Console/Schedule.php b/src/Console/Schedule.php new file mode 100644 index 000000000..33d223960 --- /dev/null +++ b/src/Console/Schedule.php @@ -0,0 +1,40 @@ +events))->filter->isDue(new FakeApp($container)); + } +} + +class FakeApp +{ + public function __construct($container) + { + $this->config = $container->make(Config::class); + } + + public function isDownForMaintenance() + { + return $this->config->inMaintenanceMode(); + } + + public function environment() + { + return ''; + } +} diff --git a/src/Extend/Console.php b/src/Extend/Console.php index 8fef2b635..044cca66c 100644 --- a/src/Extend/Console.php +++ b/src/Extend/Console.php @@ -15,6 +15,7 @@ use Illuminate\Contracts\Container\Container; class Console implements ExtenderInterface { protected $addCommands = []; + protected $scheduled = []; /** * Add a command to the console. @@ -28,10 +29,38 @@ class Console implements ExtenderInterface return $this; } + /** + * Schedule a command to run on an interval. + * + * @param string $command ::class attribute of command class, which must extend Flarum\Console\AbstractCommand + * @param callable|string $callback + * + * The callback can be a closure or invokable class, and should accept: + * - \Illuminate\Console\Scheduling\Event $event + * + * The callback should apply relevant methods to $event, and does not need to return anything. + * + * @see https://laravel.com/api/8.x/Illuminate/Console/Scheduling/Event.html + * @see https://laravel.com/docs/8.x/scheduling#schedule-frequency-options + * for more information on available methods and what they do. + * + * @param array $args An array of args to call the command with. + */ + public function schedule(string $command, $callback, $args = []) + { + $this->scheduled[] = compact('args', 'callback', 'command'); + + return $this; + } + public function extend(Container $container, Extension $extension = null) { $container->extend('flarum.console.commands', function ($existingCommands) { return array_merge($existingCommands, $this->addCommands); }); + + $container->extend('flarum.console.scheduled', function ($existingScheduled) { + return array_merge($existingScheduled, $this->scheduled); + }); } } diff --git a/src/Queue/QueueServiceProvider.php b/src/Queue/QueueServiceProvider.php index 8fac7fa74..15a03a57f 100644 --- a/src/Queue/QueueServiceProvider.php +++ b/src/Queue/QueueServiceProvider.php @@ -14,6 +14,7 @@ use Flarum\Foundation\Config; use Flarum\Foundation\ErrorHandling\Registry; use Flarum\Foundation\ErrorHandling\Reporter; use Flarum\Foundation\Paths; +use Illuminate\Contracts\Cache\Factory as CacheFactory; use Illuminate\Contracts\Debug\ExceptionHandler as ExceptionHandling; use Illuminate\Contracts\Queue\Factory; use Illuminate\Contracts\Queue\Queue; @@ -82,7 +83,7 @@ class QueueServiceProvider extends AbstractServiceProvider // Bind a simple cache manager that returns the cache store. $this->container->singleton('cache', function ($container) { - return new class($container) { + return new class($container) implements CacheFactory { public function __construct($container) { $this->container = $container; @@ -93,6 +94,13 @@ class QueueServiceProvider extends AbstractServiceProvider return $this->container['cache.store']; } + // We have to define this explicitly + // so that we implement the interface. + public function store($name = null) + { + return $this->__call($name, null); + } + public function __call($name, $arguments) { return call_user_func_array([$this->driver(), $name], $arguments); diff --git a/src/helpers.php b/src/helpers.php index fcfb2ee19..8e219155d 100644 --- a/src/helpers.php +++ b/src/helpers.php @@ -9,6 +9,7 @@ use Flarum\Foundation\Paths; use Illuminate\Container\Container; +use Illuminate\Contracts\Config\Repository; if (! function_exists('resolve')) { /** @@ -24,7 +25,6 @@ if (! function_exists('resolve')) { } } - // The following are all deprecated perpetually. // They are needed by some laravel components we use (e.g. task scheduling) // They should NOT be used in extension code. @@ -47,7 +47,7 @@ if (! function_exists('app')) { } } -if (!function_exists('base_path')) { +if (! function_exists('base_path')) { /** * @deprecated perpetually. * @@ -62,7 +62,7 @@ if (!function_exists('base_path')) { } } -if (!function_exists('public_path')) { +if (! function_exists('public_path')) { /** * @deprecated perpetually. * @@ -77,7 +77,7 @@ if (!function_exists('public_path')) { } } -if (!function_exists('storage_path')) { +if (! function_exists('storage_path')) { /** * @deprecated perpetually. * @@ -108,3 +108,13 @@ if (! function_exists('event')) { return resolve('events')->dispatch($event, $payload, $halt); } } + +if (! function_exists('config')) { + /** + * @deprecated do not use, will be transferred to flarum/laravel-helpers. + */ + function config(string $key, $default = null) + { + return resolve(Repository::class)->get($key, $default); + } +} diff --git a/tests/integration/extenders/ConsoleTest.php b/tests/integration/extenders/ConsoleTest.php index e7875120c..b47594195 100644 --- a/tests/integration/extenders/ConsoleTest.php +++ b/tests/integration/extenders/ConsoleTest.php @@ -43,6 +43,37 @@ class ConsoleTest extends ConsoleTestCase $this->assertEquals('Custom Command.', $this->runCommand($input)); } + + /** + * @test + */ + public function scheduled_command_doesnt_exist_by_default() + { + $input = [ + 'command' => 'schedule:list' + ]; + + $this->assertStringNotContainsString('cache:clear', $this->runCommand($input)); + } + + /** + * @test + */ + public function scheduled_command_exists_when_added() + { + $this->extend( + (new Extend\Console()) + ->schedule('cache:clear', function ($event) { + $event->everyMinute(); + }) + ); + + $input = [ + 'command' => 'schedule:list' + ]; + + $this->assertStringContainsString('cache:clear', $this->runCommand($input)); + } } class CustomCommand extends AbstractCommand