diff --git a/composer.json b/composer.json index 114f4549c..d2d819ae5 100644 --- a/composer.json +++ b/composer.json @@ -36,6 +36,8 @@ "illuminate/filesystem": "5.7.*", "illuminate/hashing": "5.7.*", "illuminate/mail": "5.7.*", + "illuminate/notifications": "5.7.*", + "illuminate/queue": "5.7.*", "illuminate/session": "5.7.*", "illuminate/support": "5.7.*", "illuminate/validation": "5.7.*", diff --git a/src/Foundation/InstalledSite.php b/src/Foundation/InstalledSite.php index 96bac6dc9..138799171 100644 --- a/src/Foundation/InstalledSite.php +++ b/src/Foundation/InstalledSite.php @@ -24,6 +24,7 @@ use Flarum\Locale\LocaleServiceProvider; use Flarum\Mail\MailServiceProvider; use Flarum\Notification\NotificationServiceProvider; use Flarum\Post\PostServiceProvider; +use Flarum\Queue\QueueServiceProvider; use Flarum\Search\SearchServiceProvider; use Flarum\Settings\SettingsServiceProvider; use Flarum\Update\UpdateServiceProvider; @@ -127,6 +128,7 @@ class InstalledSite implements SiteInterface $laravel->register(MigrationServiceProvider::class); $laravel->register(NotificationServiceProvider::class); $laravel->register(PostServiceProvider::class); + $laravel->register(QueueServiceProvider::class); $laravel->register(SearchServiceProvider::class); $laravel->register(SessionServiceProvider::class); $laravel->register(SettingsServiceProvider::class); @@ -178,6 +180,9 @@ class InstalledSite implements SiteInterface ] ] ], + 'queue' => [ + 'default' => 'sync' + ], 'session' => [ 'lifetime' => 120, 'files' => $this->paths['storage'].'/sessions', diff --git a/src/Queue/ExceptionHandler.php b/src/Queue/ExceptionHandler.php new file mode 100644 index 000000000..bc7998b3a --- /dev/null +++ b/src/Queue/ExceptionHandler.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Flarum\Queue; + +use Exception; +use Illuminate\Contracts\Debug\ExceptionHandler as ExceptionHandling; +use Psr\Log\LoggerInterface; + +class ExceptionHandler implements ExceptionHandling +{ + /** + * @var LoggerInterface + */ + private $logger; + + public function __construct(LoggerInterface $logger) + { + $this->logger = $logger; + } + + /** + * Report or log an exception. + * + * @param \Exception $e + * @return void + */ + public function report(Exception $e) + { + $this->logger->error((string) $e); + } + + /** + * Render an exception into an HTTP response. + * + * @param \Illuminate\Http\Request $request + * @param \Exception $e + * @return \Symfony\Component\HttpFoundation\Response + */ + public function render($request, Exception $e) + { + // TODO: Implement render() method. + } + + /** + * Render an exception to the console. + * + * @param \Symfony\Component\Console\Output\OutputInterface $output + * @param \Exception $e + * @return void + */ + public function renderForConsole($output, Exception $e) + { + // TODO: Implement renderForConsole() method. + } +} diff --git a/src/Queue/HackyManagerForWorker.php b/src/Queue/HackyManagerForWorker.php new file mode 100644 index 000000000..4802b6103 --- /dev/null +++ b/src/Queue/HackyManagerForWorker.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Flarum\Queue; + +use Illuminate\Contracts\Queue\Factory; +use Illuminate\Queue\QueueManager; + +/** + * A hacky workaround to avoid injecting an entire QueueManager (which we don't + * want to build) into Laravel's queue worker class. + * + * Laravel 6.0 will clean this up; once we upgrade, we can remove this hack and + * directly inject the factory. + */ +class HackyManagerForWorker extends QueueManager implements Factory +{ + /** + * @var Factory + */ + private $factory; + + /** + * HackyManagerForWorker constructor. + * + * Needs a real connection factory to delegate to. + * + * @param Factory $factory + */ + public function __construct(Factory $factory) + { + $this->factory = $factory; + } + + /** + * Resolve a queue connection instance. + * + * @param string $name + * @return \Illuminate\Contracts\Queue\Queue + */ + public function connection($name = null) + { + return $this->factory->connection($name); + } + + /** + * Determine if the application is in maintenance mode. + * + * @return bool + */ + public function isDownForMaintenance() + { + return false; + } +} diff --git a/src/Queue/QueueFactory.php b/src/Queue/QueueFactory.php new file mode 100644 index 000000000..f75dbb90f --- /dev/null +++ b/src/Queue/QueueFactory.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Flarum\Queue; + +use Illuminate\Contracts\Queue\Factory; + +class QueueFactory implements Factory +{ + /** + * @var callable + */ + private $factory; + + /** + * The cached queue instance. + * + * @var \Illuminate\Contracts\Queue\Queue + */ + private $queue; + + /** + * QueueFactory constructor. + * + * Expects a callback that will be called to instantiate the queue adapter, + * once requested by the application. + * + * @param callable $factory + */ + public function __construct(callable $factory) + { + $this->factory = $factory; + } + + /** + * Resolve a queue connection instance. + * + * @param string $name + * @return \Illuminate\Contracts\Queue\Queue + */ + public function connection($name = null) + { + if (is_null($this->queue)) { + $this->queue = ($this->factory)(); + } + + return $this->queue; + } +} diff --git a/src/Queue/QueueServiceProvider.php b/src/Queue/QueueServiceProvider.php new file mode 100644 index 000000000..3cdb249d2 --- /dev/null +++ b/src/Queue/QueueServiceProvider.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Flarum\Queue; + +use Flarum\Console\Event\Configuring; +use Flarum\Foundation\AbstractServiceProvider; +use Illuminate\Contracts\Debug\ExceptionHandler as ExceptionHandling; +use Illuminate\Contracts\Queue\Factory; +use Illuminate\Queue\Connectors\ConnectorInterface; +use Illuminate\Queue\Console as Commands; +use Illuminate\Queue\Failed\NullFailedJobProvider; +use Illuminate\Queue\Listener; +use Illuminate\Queue\SyncQueue; +use Illuminate\Queue\Worker; + +class QueueServiceProvider extends AbstractServiceProvider +{ + protected $commands = [ + Commands\FlushFailedCommand::class, + Commands\ForgetFailedCommand::class, + Commands\ListenCommand::class, + Commands\ListFailedCommand::class, + Commands\RestartCommand::class, + Commands\RetryCommand::class, + Commands\WorkCommand::class, + ]; + + public function register() + { + // Register a simple connection factory that always returns the same + // connection, as that is enough for our purposes. + $this->app->singleton(Factory::class, function () { + return new QueueFactory(function () { + return $this->app->make('flarum.queue.connection'); + }); + }); + + // Extensions can override this binding if they want to make Flarum use + // a different queuing backend. + $this->app->singleton('flarum.queue.connection', function ($app) { + $queue = new SyncQueue; + $queue->setContainer($app); + + return $queue; + }); + + $this->app->singleton(ExceptionHandling::class, function ($app) { + return new ExceptionHandler($app['log']); + }); + + $this->app->singleton(Worker::class, function ($app) { + return new Worker( + new HackyManagerForWorker($app[Factory::class]), + $app['events'], + $app[ExceptionHandling::class] + ); + }); + + $this->app->singleton(Listener::class, function ($app) { + return new Listener($app->basePath()); + }); + + $this->app->singleton('cache', function ($app) { + return new class($app) { + public function __construct($app) + { + $this->app = $app; + } + + public function driver() + { + return $this->app['cache.store']; + } + }; + }); + + $this->app->singleton('queue.failer', function () { + return new NullFailedJobProvider(); + }); + + $this->app->alias(ConnectorInterface::class, 'queue.connection'); + $this->app->alias(Factory::class, 'queue'); + $this->app->alias(Worker::class, 'queue.worker'); + $this->app->alias(Listener::class, 'queue.listener'); + + $this->registerCommands(); + } + + protected function registerCommands() + { + $this->app['events']->listen(Configuring::class, function (Configuring $event) { + foreach ($this->commands as $command) { + $event->addCommand($command); + } + }); + } +}