From e24b208d460b1a408dbf26fab8f5a3d41bd9908c Mon Sep 17 00:00:00 2001 From: Franz Liedke Date: Tue, 14 Aug 2018 23:15:50 +0200 Subject: [PATCH 01/13] Split up HandleErrors middleware into distinct classes These are completely distinct functionalities, toggled through the system-wide debug flag. By moving the selection of the middleware to use to the place where the middleware pipe is built, we make the middleware itself be unaware of these flags. The two classes are more focused on what they are doing, with the constructor dependencies clearly representing their requirements. In addition, this means we can just use the HandleErrorsWithWhoops middleware in the installer, which means we do not need to worry about how to inject a SettingsRepositoryInterface implementation when flarum is not yet set up. --- .../core/src/Admin/AdminServiceProvider.php | 10 ++++-- .../core/src/Forum/ForumServiceProvider.php | 10 ++++-- ...dleErrors.php => HandleErrorsWithView.php} | 18 ++-------- .../Middleware/HandleErrorsWithWhoops.php | 34 +++++++++++++++++++ 4 files changed, 51 insertions(+), 21 deletions(-) rename framework/core/src/Http/Middleware/{HandleErrors.php => HandleErrorsWithView.php} (88%) create mode 100644 framework/core/src/Http/Middleware/HandleErrorsWithWhoops.php diff --git a/framework/core/src/Admin/AdminServiceProvider.php b/framework/core/src/Admin/AdminServiceProvider.php index 3849d183c..6ec339bd2 100644 --- a/framework/core/src/Admin/AdminServiceProvider.php +++ b/framework/core/src/Admin/AdminServiceProvider.php @@ -17,7 +17,8 @@ use Flarum\Foundation\AbstractServiceProvider; use Flarum\Frontend\RecompileFrontendAssets; use Flarum\Http\Middleware\AuthenticateWithSession; use Flarum\Http\Middleware\DispatchRoute; -use Flarum\Http\Middleware\HandleErrors; +use Flarum\Http\Middleware\HandleErrorsWithView; +use Flarum\Http\Middleware\HandleErrorsWithWhoops; use Flarum\Http\Middleware\ParseJsonBody; use Flarum\Http\Middleware\RememberFromCookie; use Flarum\Http\Middleware\SetLocale; @@ -46,8 +47,11 @@ class AdminServiceProvider extends AbstractServiceProvider $pipe = new MiddlewarePipe; // All requests should first be piped through our global error handler - $debugMode = ! $app->isUpToDate() || $app->inDebugMode(); - $pipe->pipe($app->make(HandleErrors::class, ['debug' => $debugMode])); + if ($app->inDebugMode()) { + $pipe->pipe($app->make(HandleErrorsWithWhoops::class)); + } else { + $pipe->pipe($app->make(HandleErrorsWithView::class)); + } $pipe->pipe($app->make(ParseJsonBody::class)); $pipe->pipe($app->make(StartSession::class)); diff --git a/framework/core/src/Forum/ForumServiceProvider.php b/framework/core/src/Forum/ForumServiceProvider.php index 625ff636b..62655ef2a 100644 --- a/framework/core/src/Forum/ForumServiceProvider.php +++ b/framework/core/src/Forum/ForumServiceProvider.php @@ -17,7 +17,8 @@ use Flarum\Foundation\AbstractServiceProvider; use Flarum\Http\Middleware\AuthenticateWithSession; use Flarum\Http\Middleware\CollectGarbage; use Flarum\Http\Middleware\DispatchRoute; -use Flarum\Http\Middleware\HandleErrors; +use Flarum\Http\Middleware\HandleErrorsWithView; +use Flarum\Http\Middleware\HandleErrorsWithWhoops; use Flarum\Http\Middleware\ParseJsonBody; use Flarum\Http\Middleware\RememberFromCookie; use Flarum\Http\Middleware\SetLocale; @@ -49,8 +50,11 @@ class ForumServiceProvider extends AbstractServiceProvider $pipe = new MiddlewarePipe; // All requests should first be piped through our global error handler - $debugMode = ! $app->isUpToDate() || $app->inDebugMode(); - $pipe->pipe($app->make(HandleErrors::class, ['debug' => $debugMode])); + if ($app->inDebugMode()) { + $pipe->pipe($app->make(HandleErrorsWithWhoops::class)); + } else { + $pipe->pipe($app->make(HandleErrorsWithView::class)); + } $pipe->pipe($app->make(ParseJsonBody::class)); $pipe->pipe($app->make(CollectGarbage::class)); diff --git a/framework/core/src/Http/Middleware/HandleErrors.php b/framework/core/src/Http/Middleware/HandleErrorsWithView.php similarity index 88% rename from framework/core/src/Http/Middleware/HandleErrors.php rename to framework/core/src/Http/Middleware/HandleErrorsWithView.php index 8df217bf3..0ef7f5139 100644 --- a/framework/core/src/Http/Middleware/HandleErrors.php +++ b/framework/core/src/Http/Middleware/HandleErrorsWithView.php @@ -13,7 +13,6 @@ namespace Flarum\Http\Middleware; use Exception; use Flarum\Settings\SettingsRepositoryInterface; -use Franzl\Middleware\Whoops\WhoopsRunner; use Illuminate\Contracts\View\Factory as ViewFactory; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; @@ -23,7 +22,7 @@ use Psr\Log\LoggerInterface; use Symfony\Component\Translation\TranslatorInterface; use Zend\Diactoros\Response\HtmlResponse; -class HandleErrors implements Middleware +class HandleErrorsWithView implements Middleware { /** * @var ViewFactory @@ -45,25 +44,18 @@ class HandleErrors implements Middleware */ protected $settings; - /** - * @var bool - */ - protected $debug; - /** * @param ViewFactory $view * @param LoggerInterface $logger * @param TranslatorInterface $translator * @param SettingsRepositoryInterface $settings - * @param bool $debug */ - public function __construct(ViewFactory $view, LoggerInterface $logger, TranslatorInterface $translator, SettingsRepositoryInterface $settings, $debug = false) + public function __construct(ViewFactory $view, LoggerInterface $logger, TranslatorInterface $translator, SettingsRepositoryInterface $settings) { $this->view = $view; $this->logger = $logger; $this->translator = $translator; $this->settings = $settings; - $this->debug = $debug; } /** @@ -74,11 +66,7 @@ class HandleErrors implements Middleware try { return $handler->handle($request); } catch (Exception $e) { - if ($this->debug) { - return WhoopsRunner::handle($e, $request); - } else { - return $this->formatException($e); - } + return $this->formatException($e); } } diff --git a/framework/core/src/Http/Middleware/HandleErrorsWithWhoops.php b/framework/core/src/Http/Middleware/HandleErrorsWithWhoops.php new file mode 100644 index 000000000..57d56b2ad --- /dev/null +++ b/framework/core/src/Http/Middleware/HandleErrorsWithWhoops.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Flarum\Http\Middleware; + +use Exception; +use Franzl\Middleware\Whoops\WhoopsRunner; +use Psr\Http\Message\ResponseInterface as Response; +use Psr\Http\Message\ServerRequestInterface as Request; +use Psr\Http\Server\MiddlewareInterface as Middleware; +use Psr\Http\Server\RequestHandlerInterface as Handler; + +class HandleErrorsWithWhoops implements Middleware +{ + /** + * Catch all errors that happen during further middleware execution. + */ + public function process(Request $request, Handler $handler): Response + { + try { + return $handler->handle($request); + } catch (Exception $e) { + return WhoopsRunner::handle($e, $request); + } + } +} From fe07d4064be08b8e001f184f4b74692daf487da6 Mon Sep 17 00:00:00 2001 From: Franz Liedke Date: Mon, 16 Apr 2018 01:31:00 +0200 Subject: [PATCH 02/13] Split up Site into several classes Depending on the state of the Flarum installation (installed, not installed, currently upgrading, maintenance mode), we should enable different sets of service providers. For example, during installation we should not resolve a setting repository from the container. This new architecture lets us do so, but we can easily (and cleanly) register a different implementation during installation. This should prevent problems such as #1370 in the future. --- framework/core/src/Console/Server.php | 65 +--- .../core/src/Foundation/AppInterface.php | 25 ++ .../core/src/Foundation/InstalledApp.php | 132 ++++++++ .../core/src/Foundation/InstalledSite.php | 221 +++++++++++++ framework/core/src/Foundation/Site.php | 296 ++---------------- .../core/src/Foundation/SiteInterface.php | 22 ++ .../core/src/Foundation/UninstalledSite.php | 133 ++++++++ framework/core/src/Http/Server.php | 134 +------- framework/core/src/Install/Installer.php | 61 ++++ .../UninstalledSettingsRepository.php | 35 +++ 10 files changed, 683 insertions(+), 441 deletions(-) create mode 100644 framework/core/src/Foundation/AppInterface.php create mode 100644 framework/core/src/Foundation/InstalledApp.php create mode 100644 framework/core/src/Foundation/InstalledSite.php create mode 100644 framework/core/src/Foundation/SiteInterface.php create mode 100644 framework/core/src/Foundation/UninstalledSite.php create mode 100644 framework/core/src/Install/Installer.php create mode 100644 framework/core/src/Settings/UninstalledSettingsRepository.php diff --git a/framework/core/src/Console/Server.php b/framework/core/src/Console/Server.php index c5a8d6b4d..a65ef9b28 100644 --- a/framework/core/src/Console/Server.php +++ b/framework/core/src/Console/Server.php @@ -12,74 +12,37 @@ namespace Flarum\Console; use Flarum\Console\Event\Configuring; -use Flarum\Database\Console\GenerateMigrationCommand; -use Flarum\Database\Console\MigrateCommand; -use Flarum\Database\Console\ResetCommand; use Flarum\Foundation\Application; -use Flarum\Foundation\Console\CacheClearCommand; -use Flarum\Foundation\Console\InfoCommand; -use Flarum\Foundation\Site; -use Flarum\Install\Console\InstallCommand; -use Flarum\Install\InstallServiceProvider; use Illuminate\Contracts\Events\Dispatcher; use Symfony\Component\Console\Application as ConsoleApplication; class Server { - /** - * @param Site $site - * @return Server - */ - public static function fromSite(Site $site) - { - return new static($site->boot()); - } + protected $commands; - public function __construct(Application $app) + public function __construct(array $commands) { - $this->app = $app; + $this->commands = $commands; } public function listen() { - $console = $this->getConsoleApplication(); + $console = new ConsoleApplication('Flarum', Application::VERSION); + + foreach ($this->commands as $command) { + $console->add($command); + } + + $this->extend($console); exit($console->run()); } - /** - * @return ConsoleApplication - */ - protected function getConsoleApplication() + private function extend(ConsoleApplication $console) { - $console = new ConsoleApplication('Flarum', $this->app->version()); + $app = Application::getInstance(); - $this->app->register(InstallServiceProvider::class); - - $commands = [ - InstallCommand::class, - MigrateCommand::class, - ResetCommand::class, - GenerateMigrationCommand::class, - ]; - - if ($this->app->isInstalled()) { - $commands = array_merge($commands, [ - InfoCommand::class, - CacheClearCommand::class - ]); - } - - foreach ($commands as $command) { - $console->add($this->app->make( - $command, - ['config' => $this->app->isInstalled() ? $this->app->make('flarum.config') : []] - )); - } - - $events = $this->app->make(Dispatcher::class); - $events->fire(new Configuring($this->app, $console)); - - return $console; + $events = $app->make(Dispatcher::class); + $events->fire(new Configuring($app, $console)); } } diff --git a/framework/core/src/Foundation/AppInterface.php b/framework/core/src/Foundation/AppInterface.php new file mode 100644 index 000000000..927c7b42e --- /dev/null +++ b/framework/core/src/Foundation/AppInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Flarum\Foundation; + +interface AppInterface +{ + /** + * @return \Psr\Http\Server\RequestHandlerInterface + */ + public function getRequestHandler(); + + /** + * @return \Symfony\Component\Console\Command\Command[] + */ + public function getConsoleCommands(); +} diff --git a/framework/core/src/Foundation/InstalledApp.php b/framework/core/src/Foundation/InstalledApp.php new file mode 100644 index 000000000..1b0c574bb --- /dev/null +++ b/framework/core/src/Foundation/InstalledApp.php @@ -0,0 +1,132 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Flarum\Foundation; + +use Flarum\Database\Console\GenerateMigrationCommand; +use Flarum\Database\Console\MigrateCommand; +use Flarum\Database\Console\ResetCommand; +use Flarum\Foundation\Console\CacheClearCommand; +use Flarum\Foundation\Console\InfoCommand; +use Flarum\Http\Middleware\DispatchRoute; +use Flarum\Settings\SettingsRepositoryInterface; +use Zend\Diactoros\Response\HtmlResponse; +use Zend\Stratigility\MiddlewarePipe; +use function Zend\Stratigility\middleware; +use function Zend\Stratigility\path; + +class InstalledApp implements AppInterface +{ + /** + * @var Application + */ + protected $laravel; + + /** + * @var array + */ + protected $config; + + public function __construct(Application $laravel, array $config) + { + $this->laravel = $laravel; + $this->config = $config; + } + + /** + * @return \Psr\Http\Server\RequestHandlerInterface + */ + public function getRequestHandler() + { + if ($this->inMaintenanceMode()) { + return $this->getMaintenanceMiddleware(); + } elseif ($this->needsUpdate()) { + return $this->getUpdaterMiddleware(); + } + + $pipe = new MiddlewarePipe; + + $pipe->pipe($this->subPath('api', 'flarum.api.middleware')); + $pipe->pipe($this->subPath('admin', 'flarum.admin.middleware')); + $pipe->pipe($this->subPath('', 'flarum.forum.middleware')); + + return $pipe; + } + + private function inMaintenanceMode(): bool + { + return $this->config['offline'] ?? false; + } + + /** + * @return \Psr\Http\Server\RequestHandlerInterface + */ + private function getMaintenanceMiddleware() + { + $pipe = new MiddlewarePipe; + + $pipe->pipe(middleware(function () { + // FIXME: Fix path to 503.html + // TODO: FOR API render JSON-API error document for HTTP 503 + return new HtmlResponse( + file_get_contents(__DIR__.'/../../503.html'), 503 + ); + })); + + return $pipe; + } + + private function needsUpdate(): bool + { + $settings = $this->laravel->make(SettingsRepositoryInterface::class); + $version = $settings->get('version'); + + return $version !== Application::VERSION; + } + + /** + * @return \Psr\Http\Server\RequestHandlerInterface + */ + public function getUpdaterMiddleware() + { + $pipe = new MiddlewarePipe; + $pipe->pipe( + $this->laravel->make( + DispatchRoute::class, + ['routes' => $this->laravel->make('flarum.update.routes')] + ) + ); + + return $pipe; + } + + private function subPath($pathName, $middlewareStack) + { + return path( + parse_url($this->laravel->url($pathName), PHP_URL_PATH) ?: '/', + $this->laravel->make($middlewareStack) + ); + } + + /** + * @return \Symfony\Component\Console\Command\Command[] + */ + public function getConsoleCommands() + { + return [ + $this->laravel->make(GenerateMigrationCommand::class), + $this->laravel->make(InfoCommand::class, ['config' => $this->config]), + $this->laravel->make(MigrateCommand::class), + $this->laravel->make(ResetCommand::class), + $this->laravel->make(CacheClearCommand::class), + ]; + } +} diff --git a/framework/core/src/Foundation/InstalledSite.php b/framework/core/src/Foundation/InstalledSite.php new file mode 100644 index 000000000..dd6740a30 --- /dev/null +++ b/framework/core/src/Foundation/InstalledSite.php @@ -0,0 +1,221 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Flarum\Foundation; + +use Flarum\Admin\AdminServiceProvider; +use Flarum\Api\ApiServiceProvider; +use Flarum\Bus\BusServiceProvider as BusProvider; +use Flarum\Database\DatabaseServiceProvider; +use Flarum\Database\MigrationServiceProvider; +use Flarum\Discussion\DiscussionServiceProvider; +use Flarum\Extension\ExtensionServiceProvider; +use Flarum\Formatter\FormatterServiceProvider; +use Flarum\Forum\ForumServiceProvider; +use Flarum\Frontend\FrontendServiceProvider; +use Flarum\Group\GroupServiceProvider; +use Flarum\Locale\LocaleServiceProvider; +use Flarum\Notification\NotificationServiceProvider; +use Flarum\Post\PostServiceProvider; +use Flarum\Search\SearchServiceProvider; +use Flarum\Settings\SettingsRepositoryInterface; +use Flarum\Settings\SettingsServiceProvider; +use Flarum\Update\UpdateServiceProvider; +use Flarum\User\UserServiceProvider; +use Illuminate\Cache\FileStore; +use Illuminate\Cache\Repository as CacheRepository; +use Illuminate\Config\Repository as ConfigRepository; +use Illuminate\Contracts\Cache\Repository; +use Illuminate\Contracts\Cache\Store; +use Illuminate\Filesystem\Filesystem; +use Illuminate\Filesystem\FilesystemServiceProvider; +use Illuminate\Hashing\HashServiceProvider; +use Illuminate\Mail\MailServiceProvider; +use Illuminate\Validation\ValidationServiceProvider; +use Illuminate\View\ViewServiceProvider; +use Monolog\Formatter\LineFormatter; +use Monolog\Handler\StreamHandler; +use Monolog\Logger; +use Psr\Log\LoggerInterface; + +class InstalledSite implements SiteInterface +{ + /** + * @var string + */ + protected $basePath; + + /** + * @var string + */ + protected $publicPath; + + /** + * @var string + */ + protected $storagePath; + + /** + * @var array + */ + protected $config; + + public function __construct($basePath, $publicPath, array $config) + { + $this->basePath = $basePath; + $this->publicPath = $publicPath; + $this->config = $config; + } + + /** + * Create and boot a Flarum application instance. + * + * @return AppInterface + */ + public function bootApp(): AppInterface + { + return new InstalledApp( + $this->bootLaravel(), + $this->config + ); + } + + /** + * @param $storagePath + * @return static + */ + public function setStoragePath($storagePath) + { + $this->storagePath = $storagePath; + + return $this; + } + + protected function bootLaravel(): Application + { + $laravel = new Application($this->basePath, $this->publicPath); + + if ($this->storagePath) { + $laravel->useStoragePath($this->storagePath); + } + + $laravel->instance('env', 'production'); + $laravel->instance('flarum.config', $this->config); + $laravel->instance('config', $config = $this->getIlluminateConfig($laravel)); + + $this->registerLogger($laravel); + $this->registerCache($laravel); + + $laravel->register(DatabaseServiceProvider::class); + $laravel->register(MigrationServiceProvider::class); + $laravel->register(SettingsServiceProvider::class); + $laravel->register(LocaleServiceProvider::class); + $laravel->register(BusServiceProvider::class); + $laravel->register(FilesystemServiceProvider::class); + $laravel->register(HashServiceProvider::class); + $laravel->register(MailServiceProvider::class); + $laravel->register(ViewServiceProvider::class); + $laravel->register(ValidationServiceProvider::class); + + $laravel->register(BusProvider::class); + + $settings = $laravel->make(SettingsRepositoryInterface::class); + + $config->set('mail.driver', $settings->get('mail_driver')); + $config->set('mail.host', $settings->get('mail_host')); + $config->set('mail.port', $settings->get('mail_port')); + $config->set('mail.from.address', $settings->get('mail_from')); + $config->set('mail.from.name', $settings->get('forum_title')); + $config->set('mail.encryption', $settings->get('mail_encryption')); + $config->set('mail.username', $settings->get('mail_username')); + $config->set('mail.password', $settings->get('mail_password')); + + $laravel->register(DiscussionServiceProvider::class); + $laravel->register(FormatterServiceProvider::class); + $laravel->register(FrontendServiceProvider::class); + $laravel->register(GroupServiceProvider::class); + $laravel->register(NotificationServiceProvider::class); + $laravel->register(PostServiceProvider::class); + $laravel->register(SearchServiceProvider::class); + $laravel->register(UserServiceProvider::class); + $laravel->register(UpdateServiceProvider::class); + + $laravel->register(ApiServiceProvider::class); + $laravel->register(ForumServiceProvider::class); + $laravel->register(AdminServiceProvider::class); + + $laravel->register(ExtensionServiceProvider::class); + + $laravel->boot(); + + return $laravel; + } + + /** + * @param Application $app + * @return ConfigRepository + */ + protected function getIlluminateConfig(Application $app) + { + return new ConfigRepository([ + 'view' => [ + 'paths' => [], + 'compiled' => $app->storagePath().'/views', + ], + 'mail' => [ + 'driver' => 'mail', + ], + 'filesystems' => [ + 'default' => 'local', + 'cloud' => 's3', + 'disks' => [ + 'flarum-assets' => [ + 'driver' => 'local', + 'root' => $app->publicPath().'/assets', + 'url' => $app->url('assets') + ], + 'flarum-avatars' => [ + 'driver' => 'local', + 'root' => $app->publicPath().'/assets/avatars' + ] + ] + ], + 'session' => [ + 'lifetime' => 120, + 'files' => $app->storagePath().'/sessions', + 'cookie' => 'session' + ] + ]); + } + + protected function registerLogger(Application $app) + { + $logPath = $app->storagePath().'/logs/flarum.log'; + $handler = new StreamHandler($logPath, Logger::INFO); + $handler->setFormatter(new LineFormatter(null, null, true, true)); + + $app->instance('log', new Logger($app->environment(), [$handler])); + $app->alias('log', LoggerInterface::class); + } + + protected function registerCache(Application $app) + { + $app->singleton('cache.store', function ($app) { + return new CacheRepository($app->make('cache.filestore')); + }); + $app->alias('cache.store', Repository::class); + + $app->singleton('cache.filestore', function ($app) { + return new FileStore(new Filesystem, $app->storagePath().'/cache'); + }); + $app->alias('cache.filestore', Store::class); + } +} diff --git a/framework/core/src/Foundation/Site.php b/framework/core/src/Foundation/Site.php index 14a4f90da..dc58cb593 100644 --- a/framework/core/src/Foundation/Site.php +++ b/framework/core/src/Foundation/Site.php @@ -11,281 +11,53 @@ namespace Flarum\Foundation; -use Flarum\Admin\AdminServiceProvider; -use Flarum\Api\ApiServiceProvider; -use Flarum\Bus\BusServiceProvider as BusProvider; -use Flarum\Database\DatabaseServiceProvider; -use Flarum\Database\MigrationServiceProvider; -use Flarum\Discussion\DiscussionServiceProvider; -use Flarum\Extension\ExtensionServiceProvider; -use Flarum\Formatter\FormatterServiceProvider; -use Flarum\Forum\ForumServiceProvider; -use Flarum\Frontend\FrontendServiceProvider; -use Flarum\Group\GroupServiceProvider; -use Flarum\Locale\LocaleServiceProvider; -use Flarum\Notification\NotificationServiceProvider; -use Flarum\Post\PostServiceProvider; -use Flarum\Search\SearchServiceProvider; -use Flarum\Settings\SettingsRepositoryInterface; -use Flarum\Settings\SettingsServiceProvider; -use Flarum\User\UserServiceProvider; -use Illuminate\Bus\BusServiceProvider; -use Illuminate\Config\Repository as ConfigRepository; -use Illuminate\Filesystem\FilesystemServiceProvider; -use Illuminate\Hashing\HashServiceProvider; -use Illuminate\Mail\MailServiceProvider; -use Illuminate\Validation\ValidationServiceProvider; -use Illuminate\View\ViewServiceProvider; -use Monolog\Formatter\LineFormatter; -use Monolog\Handler\StreamHandler; -use Monolog\Logger; +use InvalidArgumentException; +use RuntimeException; -// TODO: This should be an interface maybe? class Site { /** - * @var Application + * @param array $paths + * @return SiteInterface */ - protected $app; - - /** - * @var string - */ - protected $basePath; - - /** - * @var string - */ - protected $publicPath; - - /** - * @var string - */ - protected $storagePath; - - /** - * @var array - */ - protected $config; - - protected $extenders = []; - - public function __construct() + public static function fromPaths(array $paths) { - $this->basePath = getcwd(); - $this->publicPath = $this->basePath; - } - - /** - * @return Application - */ - public function boot() - { - return $this->getApp(); - } - - /** - * @param $basePath - * @return static - */ - public function setBasePath($basePath) - { - $this->basePath = $basePath; - - return $this; - } - - /** - * @param $publicPath - * @return static - */ - public function setPublicPath($publicPath) - { - $this->publicPath = $publicPath; - - return $this; - } - - /** - * @param $storagePath - * @return static - */ - public function setStoragePath($storagePath) - { - $this->storagePath = $storagePath; - - return $this; - } - - /** - * @param array $config - * @return static - */ - public function setConfig(array $config) - { - $this->config = $config; - - return $this; - } - - protected function getConfig() - { - if (empty($this->config) && file_exists($file = $this->basePath.'/config.php')) { - $this->config = include $file; + if (! isset($paths['base'])) { + throw new InvalidArgumentException( + 'No base path given' + ); } - return $this->config; - } - - /** - * @return Application - */ - protected function getApp() - { - if ($this->app !== null) { - return $this->app; + if (! isset($paths['public'])) { + $paths['public'] = $paths['base']; } date_default_timezone_set('UTC'); - $app = new Application($this->basePath, $this->publicPath); - - if ($this->storagePath) { - $app->useStoragePath($this->storagePath); - } - - $app->instance('env', 'production'); - $app->instance('flarum.config', $this->getConfig()); - $app->instance('config', $config = $this->getIlluminateConfig($app)); - - $this->registerLogger($app); - - $this->registerCache($app); - - $app->register(DatabaseServiceProvider::class); - $app->register(MigrationServiceProvider::class); - $app->register(SettingsServiceProvider::class); - $app->register(LocaleServiceProvider::class); - $app->register(BusServiceProvider::class); - $app->register(FilesystemServiceProvider::class); - $app->register(HashServiceProvider::class); - $app->register(MailServiceProvider::class); - $app->register(ViewServiceProvider::class); - $app->register(ValidationServiceProvider::class); - - $app->register(BusProvider::class); - - if ($app->isInstalled() && $app->isUpToDate()) { - $settings = $app->make(SettingsRepositoryInterface::class); - - $config->set('mail.driver', $settings->get('mail_driver')); - $config->set('mail.host', $settings->get('mail_host')); - $config->set('mail.port', $settings->get('mail_port')); - $config->set('mail.from.address', $settings->get('mail_from')); - $config->set('mail.from.name', $settings->get('forum_title')); - $config->set('mail.encryption', $settings->get('mail_encryption')); - $config->set('mail.username', $settings->get('mail_username')); - $config->set('mail.password', $settings->get('mail_password')); - - $app->register(DiscussionServiceProvider::class); - $app->register(FormatterServiceProvider::class); - $app->register(FrontendServiceProvider::class); - $app->register(GroupServiceProvider::class); - $app->register(NotificationServiceProvider::class); - $app->register(PostServiceProvider::class); - $app->register(SearchServiceProvider::class); - $app->register(UserServiceProvider::class); - - $app->register(ApiServiceProvider::class); - $app->register(ForumServiceProvider::class); - $app->register(AdminServiceProvider::class); - - foreach ($this->extenders as $extender) { - // TODO: Create extenders architecture - // $extender->apply($app); - } - - $app->register(ExtensionServiceProvider::class); - } - - $app->boot(); - - $this->app = $app; - - return $app; - } - - /** - * @param Application $app - * @return ConfigRepository - */ - protected function getIlluminateConfig(Application $app) - { - return new ConfigRepository([ - 'view' => [ - 'paths' => [], - 'compiled' => $app->storagePath().'/views', - ], - 'mail' => [ - 'driver' => 'mail', - ], - 'filesystems' => [ - 'default' => 'local', - 'cloud' => 's3', - 'disks' => [ - 'flarum-assets' => [ - 'driver' => 'local', - 'root' => $app->publicPath().'/assets', - 'url' => $app->url('assets') - ], - 'flarum-avatars' => [ - 'driver' => 'local', - 'root' => $app->publicPath().'/assets/avatars' - ] - ] - ], - 'session' => [ - 'lifetime' => 120, - 'files' => $app->storagePath().'/sessions', - 'cookie' => 'session' - ] - ]); - } - - /** - * @param Application $app - */ - protected function registerLogger(Application $app) - { - $logger = new Logger($app->environment()); - $logPath = $app->storagePath().'/logs/flarum.log'; - - $handler = new StreamHandler($logPath, Logger::DEBUG); - $handler->setFormatter(new LineFormatter(null, null, true, true)); - - $logger->pushHandler($handler); - - $app->instance('log', $logger); - $app->alias('log', 'Psr\Log\LoggerInterface'); - } - - /** - * @param Application $app - */ - protected function registerCache(Application $app) - { - $app->singleton('cache.store', function ($app) { - return new \Illuminate\Cache\Repository($app->make('cache.filestore')); - }); - - $app->singleton('cache.filestore', function ($app) { - return new \Illuminate\Cache\FileStore( - new \Illuminate\Filesystem\Filesystem(), - $app->storagePath().'/cache' + if (static::hasConfigFile($paths['base'])) { + return new InstalledSite( + $paths['base'], + $paths['public'], + static::loadConfig($paths['base']) ); - }); + } else { + return new UninstalledSite($paths['base'], $paths['public']); + } + } - $app->alias('cache.filestore', 'Illuminate\Contracts\Cache\Store'); - $app->alias('cache.store', 'Illuminate\Contracts\Cache\Repository'); + private static function hasConfigFile($basePath) + { + return file_exists("$basePath/config.php"); + } + + private static function loadConfig($basePath): array + { + $config = include "$basePath/config.php"; + + if (! is_array($config)) { + throw new RuntimeException('config.php should return an array'); + } + + return $config; } } diff --git a/framework/core/src/Foundation/SiteInterface.php b/framework/core/src/Foundation/SiteInterface.php new file mode 100644 index 000000000..9f5b1b97f --- /dev/null +++ b/framework/core/src/Foundation/SiteInterface.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Flarum\Foundation; + +interface SiteInterface +{ + /** + * Create and boot a Flarum application instance. + * + * @return AppInterface + */ + public function bootApp(): AppInterface; +} diff --git a/framework/core/src/Foundation/UninstalledSite.php b/framework/core/src/Foundation/UninstalledSite.php new file mode 100644 index 000000000..b655e150d --- /dev/null +++ b/framework/core/src/Foundation/UninstalledSite.php @@ -0,0 +1,133 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Flarum\Foundation; + +use Flarum\Install\Installer; +use Flarum\Install\InstallServiceProvider; +use Flarum\Locale\LocaleServiceProvider; +use Flarum\Settings\SettingsRepositoryInterface; +use Flarum\Settings\UninstalledSettingsRepository; +use Illuminate\Config\Repository as ConfigRepository; +use Illuminate\Contracts\Events\Dispatcher; +use Illuminate\Filesystem\FilesystemServiceProvider; +use Illuminate\Validation\ValidationServiceProvider; +use Illuminate\View\Engines\EngineResolver; +use Illuminate\View\Engines\PhpEngine; +use Illuminate\View\FileViewFinder; +use Monolog\Formatter\LineFormatter; +use Monolog\Handler\StreamHandler; +use Monolog\Logger; +use Psr\Log\LoggerInterface; + +class UninstalledSite implements SiteInterface +{ + /** + * @var string + */ + protected $basePath; + + /** + * @var string + */ + protected $publicPath; + + /** + * @var string + */ + protected $storagePath; + + public function __construct($basePath, $publicPath) + { + $this->basePath = $basePath; + $this->publicPath = $publicPath; + } + + /** + * Create and boot a Flarum application instance. + * + * @return AppInterface + */ + public function bootApp(): AppInterface + { + return new Installer( + $this->bootLaravel() + ); + } + + private function bootLaravel(): Application + { + $laravel = new Application($this->basePath, $this->publicPath); + + if ($this->storagePath) { + $laravel->useStoragePath($this->storagePath); + } + + $laravel->instance('env', 'production'); + $laravel->instance('flarum.config', []); + $laravel->instance('config', $config = $this->getIlluminateConfig($laravel)); + + $this->registerLogger($laravel); + + $laravel->register(LocaleServiceProvider::class); + $laravel->register(FilesystemServiceProvider::class); + $laravel->register(ValidationServiceProvider::class); + + $laravel->register(InstallServiceProvider::class); + + $laravel->singleton( + SettingsRepositoryInterface::class, + UninstalledSettingsRepository::class + ); + + $laravel->singleton('view', function ($app) { + $engines = new EngineResolver(); + $engines->register('php', function () { + return new PhpEngine(); + }); + $finder = new FileViewFinder($app->make('files'), []); + $dispatcher = $app->make(Dispatcher::class); + + return new \Illuminate\View\Factory( + $engines, $finder, $dispatcher + ); + }); + + $laravel->boot(); + + return $laravel; + } + + /** + * @param Application $app + * @return ConfigRepository + */ + protected function getIlluminateConfig(Application $app) + { + return new ConfigRepository([ + 'session' => [ + 'lifetime' => 120, + 'files' => $app->storagePath().'/sessions', + 'cookie' => 'session' + ] + ]); + } + + protected function registerLogger(Application $app) + { + $logPath = $app->storagePath().'/logs/flarum-installer.log'; + $handler = new StreamHandler($logPath, Logger::DEBUG); + $handler->setFormatter(new LineFormatter(null, null, true, true)); + + $app->instance('log', new Logger('Flarum Installer', [$handler])); + $app->alias('log', LoggerInterface::class); + } +} diff --git a/framework/core/src/Http/Server.php b/framework/core/src/Http/Server.php index 9583d9180..825dc4bce 100644 --- a/framework/core/src/Http/Server.php +++ b/framework/core/src/Http/Server.php @@ -11,43 +11,22 @@ namespace Flarum\Http; -use Flarum\Foundation\Application; -use Flarum\Foundation\Site; -use Flarum\Http\Middleware\DispatchRoute; -use Flarum\Http\Middleware\HandleErrors; -use Flarum\Http\Middleware\StartSession; -use Flarum\Install\InstallServiceProvider; -use Flarum\Update\UpdateServiceProvider; -use Psr\Http\Message\ResponseInterface as Response; -use Psr\Http\Message\ServerRequestInterface as Request; -use Psr\Http\Server\MiddlewareInterface as Middleware; -use Psr\Http\Server\RequestHandlerInterface as Handler; -use Zend\Diactoros\Response\HtmlResponse; +use Psr\Http\Server\RequestHandlerInterface; use Zend\Diactoros\Server as DiactorosServer; -use Zend\Stratigility\MiddlewarePipe; -use function Zend\Stratigility\middleware; -use function Zend\Stratigility\path; -class Server implements Middleware, Handler +class Server { - /** - * @param Site $site - * @return Server - */ - public static function fromSite(Site $site) - { - return new static($site->boot()); - } + protected $requestHandler; - public function __construct(Application $app) + public function __construct(RequestHandlerInterface $requestHandler) { - $this->app = $app; + $this->requestHandler = $requestHandler; } public function listen() { DiactorosServer::createServer( - [$this, 'handle'], + [$this->requestHandler, 'handle'], $_SERVER, $_GET, $_POST, @@ -55,105 +34,4 @@ class Server implements Middleware, Handler $_FILES )->listen(); } - - /** - * Use as PSR-15 middleware. - */ - public function process(Request $request, Handler $handler): Response - { - $middleware = $this->getMiddleware($request->getUri()->getPath()); - - return $middleware->process($request, $handler); - } - - /** - * Use as PSR-15 request handler. - */ - public function handle(Request $request): Response - { - $middleware = $this->getMiddleware($request->getUri()->getPath()); - - return $middleware->handle($request); - } - - /** - * @param string $requestPath - * @return MiddlewarePipe - */ - protected function getMiddleware($requestPath) - { - $pipe = new MiddlewarePipe; - - if (! $this->app->isInstalled()) { - return $this->getInstallerMiddleware($pipe); - } - - if ($this->app->isDownForMaintenance()) { - return $this->getMaintenanceMiddleware($pipe); - } - - if (! $this->app->isUpToDate()) { - return $this->getUpdaterMiddleware($pipe); - } - - $api = parse_url($this->app->url('api'), PHP_URL_PATH); - $admin = parse_url($this->app->url('admin'), PHP_URL_PATH); - $forum = parse_url($this->app->url(''), PHP_URL_PATH) ?: '/'; - - if ($this->pathStartsWith($requestPath, $api)) { - $pipe->pipe(path($api, $this->app->make('flarum.api.middleware'))); - } elseif ($this->pathStartsWith($requestPath, $admin)) { - $pipe->pipe(path($admin, $this->app->make('flarum.admin.middleware'))); - } else { - $pipe->pipe(path($forum, $this->app->make('flarum.forum.middleware'))); - } - - return $pipe; - } - - private function pathStartsWith($path, $prefix) - { - return $path === $prefix || starts_with($path, "$prefix/"); - } - - protected function getInstallerMiddleware(MiddlewarePipe $pipe) - { - $this->app->register(InstallServiceProvider::class); - - // FIXME: Re-enable HandleErrors middleware, if possible - // (Right now it tries to resolve a database connection because of the injected settings repo instance) - // We could register a different settings repo when Flarum is not installed - //$pipe->pipe($this->app->make(HandleErrors::class, ['debug' => true])); - //$pipe->pipe($this->app->make(StartSession::class)); - $pipe->pipe($this->app->make(DispatchRoute::class, ['routes' => $this->app->make('flarum.install.routes')])); - - return $pipe; - } - - protected function getMaintenanceMiddleware(MiddlewarePipe $pipe) - { - $pipe->pipe(middleware(function () { - return new HtmlResponse(file_get_contents($this->getErrorDir().'/503.html', 503)); - })); - - // TODO: FOR API render JSON-API error document for HTTP 503 - - return $pipe; - } - - protected function getUpdaterMiddleware(MiddlewarePipe $pipe) - { - $this->app->register(UpdateServiceProvider::class); - - $pipe->pipe($this->app->make(DispatchRoute::class, ['routes' => $this->app->make('flarum.update.routes')])); - - // TODO: FOR API render JSON-API error document for HTTP 503 - - return $pipe; - } - - private function getErrorDir() - { - return __DIR__.'/../../error'; - } } diff --git a/framework/core/src/Install/Installer.php b/framework/core/src/Install/Installer.php new file mode 100644 index 000000000..0ca0253e7 --- /dev/null +++ b/framework/core/src/Install/Installer.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Flarum\Install; + +use Flarum\Foundation\AppInterface; +use Flarum\Foundation\Application; +use Flarum\Http\Middleware\DispatchRoute; +use Flarum\Http\Middleware\HandleErrorsWithWhoops; +use Flarum\Http\Middleware\StartSession; +use Flarum\Install\Console\InstallCommand; +use Zend\Stratigility\MiddlewarePipe; + +class Installer implements AppInterface +{ + /** + * @var Application + */ + protected $laravel; + + public function __construct(Application $laravel) + { + $this->laravel = $laravel; + } + + /** + * @return \Psr\Http\Server\RequestHandlerInterface + */ + public function getRequestHandler() + { + $pipe = new MiddlewarePipe; + $pipe->pipe($this->laravel->make(HandleErrorsWithWhoops::class)); + #$pipe->pipe($this->laravel->make(StartSession::class)); + $pipe->pipe( + $this->laravel->make( + DispatchRoute::class, + ['routes' => $this->laravel->make('flarum.install.routes')] + ) + ); + + return $pipe; + } + + /** + * @return \Symfony\Component\Console\Command\Command[] + */ + public function getConsoleCommands() + { + return [ + $this->laravel->make(InstallCommand::class), + ]; + } +} diff --git a/framework/core/src/Settings/UninstalledSettingsRepository.php b/framework/core/src/Settings/UninstalledSettingsRepository.php new file mode 100644 index 000000000..fb8d1af34 --- /dev/null +++ b/framework/core/src/Settings/UninstalledSettingsRepository.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Flarum\Settings; + +class UninstalledSettingsRepository implements SettingsRepositoryInterface +{ + public function all() + { + return []; + } + + public function get($key, $default = null) + { + return $default; + } + + public function set($key, $value) + { + // Do nothing + } + + public function delete($keyLike) + { + // Do nothing + } +} From dccbefeafa9408d12ac9e30e4b0b924c59b79478 Mon Sep 17 00:00:00 2001 From: Franz Liedke Date: Sat, 21 Apr 2018 18:57:15 +0200 Subject: [PATCH 03/13] Get rid of some Application methods These are not necessary to be available so broadly. In fact, they seem to make it too easy to use them, which has lead to some sub- optimal architecture decisions. Their equivalents have been moved to the classes where used. --- .../src/Database/DatabaseServiceProvider.php | 6 ++-- framework/core/src/Foundation/Application.php | 28 ++----------------- .../core/src/Locale/LocaleServiceProvider.php | 7 +++-- 3 files changed, 9 insertions(+), 32 deletions(-) diff --git a/framework/core/src/Database/DatabaseServiceProvider.php b/framework/core/src/Database/DatabaseServiceProvider.php index 8d3c08262..302305266 100644 --- a/framework/core/src/Database/DatabaseServiceProvider.php +++ b/framework/core/src/Database/DatabaseServiceProvider.php @@ -50,9 +50,7 @@ class DatabaseServiceProvider extends AbstractServiceProvider */ public function boot() { - if ($this->app->isInstalled()) { - AbstractModel::setConnectionResolver($this->app->make('Illuminate\Database\ConnectionResolverInterface')); - AbstractModel::setEventDispatcher($this->app->make('events')); - } + AbstractModel::setConnectionResolver($this->app->make('Illuminate\Database\ConnectionResolverInterface')); + AbstractModel::setEventDispatcher($this->app->make('events')); } } diff --git a/framework/core/src/Foundation/Application.php b/framework/core/src/Foundation/Application.php index 61c8fd798..10c194520 100644 --- a/framework/core/src/Foundation/Application.php +++ b/framework/core/src/Foundation/Application.php @@ -11,7 +11,6 @@ namespace Flarum\Foundation; -use Flarum\Settings\SettingsRepositoryInterface; use Illuminate\Container\Container; use Illuminate\Contracts\Foundation\Application as ApplicationContract; use Illuminate\Events\EventServiceProvider; @@ -114,27 +113,6 @@ class Application extends Container implements ApplicationContract } } - /** - * Determine if Flarum has been installed. - * - * @return bool - */ - public function isInstalled(): bool - { - return $this->bound('flarum.config'); - } - - public function isUpToDate(): bool - { - $settings = $this->make(SettingsRepositoryInterface::class); - - try { - $version = $settings->get('version'); - } finally { - return isset($version) && $version === $this->version(); - } - } - /** * @param string $key * @param mixed $default @@ -142,7 +120,7 @@ class Application extends Container implements ApplicationContract */ public function config($key, $default = null) { - return $this->isInstalled() ? array_get($this->make('flarum.config'), $key, $default) : $default; + return array_get($this->make('flarum.config'), $key, $default); } /** @@ -152,7 +130,7 @@ class Application extends Container implements ApplicationContract */ public function inDebugMode() { - return ! $this->isInstalled() || $this->config('debug'); + return $this->config('debug', true); } /** @@ -163,7 +141,7 @@ class Application extends Container implements ApplicationContract */ public function url($path = null) { - $config = $this->isInstalled() ? $this->make('flarum.config') : []; + $config = $this->make('flarum.config'); $url = array_get($config, 'url', array_get($_SERVER, 'REQUEST_URI')); if (is_array($url)) { diff --git a/framework/core/src/Locale/LocaleServiceProvider.php b/framework/core/src/Locale/LocaleServiceProvider.php index 74ab5331d..b9cf94b68 100644 --- a/framework/core/src/Locale/LocaleServiceProvider.php +++ b/framework/core/src/Locale/LocaleServiceProvider.php @@ -13,6 +13,7 @@ namespace Flarum\Locale; use Flarum\Event\ConfigureLocales; use Flarum\Foundation\AbstractServiceProvider; +use Flarum\Settings\SettingsRepositoryInterface; use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Contracts\Translation\Translator as TranslatorContract; use Symfony\Component\Translation\MessageSelector; @@ -54,8 +55,8 @@ class LocaleServiceProvider extends AbstractServiceProvider private function getDefaultLocale(): string { - return $this->app->isInstalled() && $this->app->isUpToDate() - ? $this->app->make('flarum.settings')->get('default_locale', 'en') - : 'en'; + $repo = $this->app->make(SettingsRepositoryInterface::class); + + return $repo->get('default_locale', 'en'); } } From 61834b006f7eedc1b8c060ee8a539e0817ca0b67 Mon Sep 17 00:00:00 2001 From: Franz Liedke Date: Wed, 25 Apr 2018 00:02:39 +0200 Subject: [PATCH 04/13] Console Installer: Rely less on service providers Most things we need, we can instantiate directly. This means we have to do less tweaking in service providers that are meant to provide services to a complete Flarum application (that has already been installed properly), to make them work with the uninstalled app. The uninstalled app (the "installer") can now do only the bootstrapping it needs to build a light-weight web and console application, respectively. --- .../src/Install/Console/InstallCommand.php | 102 +++++++++--------- 1 file changed, 50 insertions(+), 52 deletions(-) diff --git a/framework/core/src/Install/Console/InstallCommand.php b/framework/core/src/Install/Console/InstallCommand.php index ed6b07acb..00149cb94 100644 --- a/framework/core/src/Install/Console/InstallCommand.php +++ b/framework/core/src/Install/Console/InstallCommand.php @@ -13,26 +13,19 @@ namespace Flarum\Install\Console; use Exception; use Flarum\Console\AbstractCommand; -use Flarum\Database\AbstractModel; +use Flarum\Database\DatabaseMigrationRepository; use Flarum\Database\Migrator; -use Flarum\Discussion\DiscussionServiceProvider; use Flarum\Extension\ExtensionManager; -use Flarum\Formatter\FormatterServiceProvider; use Flarum\Group\Group; -use Flarum\Group\GroupServiceProvider; use Flarum\Install\Prerequisite\PrerequisiteInterface; -use Flarum\Notification\NotificationServiceProvider; -use Flarum\Post\PostServiceProvider; -use Flarum\Search\SearchServiceProvider; -use Flarum\Settings\SettingsRepositoryInterface; -use Flarum\User\User; -use Flarum\User\UserServiceProvider; +use Flarum\Settings\DatabaseSettingsRepository; +use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Contracts\Foundation\Application; use Illuminate\Contracts\Translation\Translator; use Illuminate\Database\ConnectionInterface; -use Illuminate\Database\ConnectionResolverInterface; -use Illuminate\Database\Schema\Builder; +use Illuminate\Database\Connectors\ConnectionFactory; use Illuminate\Filesystem\Filesystem; +use Illuminate\Hashing\BcryptHasher; use Illuminate\Validation\Factory; use PDO; use Symfony\Component\Console\Input\InputOption; @@ -54,6 +47,16 @@ class InstallCommand extends AbstractCommand */ protected $filesystem; + /** + * @var ConnectionInterface + */ + protected $db; + + /** + * @var Migrator + */ + protected $migrator; + /** * @param Application $application * @param Filesystem $filesystem @@ -177,22 +180,10 @@ class InstallCommand extends AbstractCommand $this->storeConfiguration($this->dataSource->isDebugMode()); - $resolver = $this->application->make(ConnectionResolverInterface::class); - AbstractModel::setConnectionResolver($resolver); - AbstractModel::setEventDispatcher($this->application->make('events')); - $this->runMigrations(); $this->writeSettings(); - $this->application->register(UserServiceProvider::class); - $this->application->register(FormatterServiceProvider::class); - $this->application->register(DiscussionServiceProvider::class); - $this->application->register(GroupServiceProvider::class); - $this->application->register(NotificationServiceProvider::class); - $this->application->register(SearchServiceProvider::class); - $this->application->register(PostServiceProvider::class); - $this->seedGroups(); $this->createAdminUser(); @@ -213,7 +204,7 @@ class InstallCommand extends AbstractCommand $config = [ 'debug' => $debugMode, - 'database' => [ + 'database' => $laravelDbConfig = [ 'driver' => $dbConfig['driver'], 'host' => $dbConfig['host'], 'database' => $dbConfig['database'], @@ -234,15 +225,22 @@ class InstallCommand extends AbstractCommand $this->info('Testing config'); - $this->application->instance('flarum.config', $config); - /* @var $db \Illuminate\Database\ConnectionInterface */ - $db = $this->application->make('flarum.db'); - $version = $db->getPdo()->getAttribute(PDO::ATTR_SERVER_VERSION); + $factory = new ConnectionFactory($this->application); + + $this->db = $factory->make($laravelDbConfig); + $version = $this->db->getPdo()->getAttribute(PDO::ATTR_SERVER_VERSION); if (version_compare($version, '5.5.0', '<')) { throw new Exception('MySQL version too low. You need at least MySQL 5.5.'); } + $repository = new DatabaseMigrationRepository( + $this->db, 'migrations' + ); + $files = $this->application->make('files'); + + $this->migrator = new Migrator($repository, $this->db, $files); + $this->info('Writing config'); file_put_contents( @@ -253,23 +251,17 @@ class InstallCommand extends AbstractCommand protected function runMigrations() { - $this->application->bind(Builder::class, function ($container) { - return $container->make(ConnectionInterface::class)->getSchemaBuilder(); - }); + $this->migrator->getRepository()->createRepository(); + $this->migrator->run(__DIR__.'/../../../migrations'); - $migrator = $this->application->make(Migrator::class); - $migrator->getRepository()->createRepository(); - - $migrator->run(__DIR__.'/../../../migrations'); - - foreach ($migrator->getNotes() as $note) { + foreach ($this->migrator->getNotes() as $note) { $this->info($note); } } protected function writeSettings() { - $settings = $this->application->make(SettingsRepositoryInterface::class); + $settings = new DatabaseSettingsRepository($this->db); $this->info('Writing default settings'); @@ -282,8 +274,6 @@ class InstallCommand extends AbstractCommand protected function seedGroups() { - Group::unguard(); - $groups = [ [Group::ADMINISTRATOR_ID, 'Admin', 'Admins', '#B72A2A', 'fas fa-wrench'], [Group::GUEST_ID, 'Guest', 'Guests', null, null], @@ -292,7 +282,7 @@ class InstallCommand extends AbstractCommand ]; foreach ($groups as $group) { - Group::create([ + $this->db->table('groups')->insert([ 'id' => $group[0], 'name_singular' => $group[1], 'name_plural' => $group[2], @@ -312,21 +302,29 @@ class InstallCommand extends AbstractCommand $this->info('Creating admin user '.$admin['username']); - $user = User::register( - $admin['username'], - $admin['email'], - $admin['password'] - ); + $uid = $this->db->table('users')->insertGetId([ + 'username' => $admin['username'], + 'email' => $admin['email'], + 'password' => (new BcryptHasher)->make($admin['password']), + 'join_time' => time(), + 'is_activated' => 1, + ]); - $user->is_activated = 1; - $user->save(); - - $user->groups()->sync([Group::ADMINISTRATOR_ID]); + $this->db->table('users_groups')->insert([ + 'user_id' => $uid, + 'group_id' => Group::ADMINISTRATOR_ID, + ]); } protected function enableBundledExtensions() { - $extensions = $this->application->make(ExtensionManager::class); + $extensions = new ExtensionManager( + new DatabaseSettingsRepository($this->db), + $this->application, + $this->migrator, + $this->application->make(Dispatcher::class), + $this->application->make('files') + ); $migrator = $extensions->getMigrator(); From 2d4802d63748351f451ebcbdad323006b3610682 Mon Sep 17 00:00:00 2001 From: Franz Liedke Date: Thu, 2 Aug 2018 23:46:39 +0200 Subject: [PATCH 05/13] Use zend-httphandlerrunner for marshalling requests and returning responses Since Diactoros 1.8, the emitter and server classes have been deprecated. They can be replaced by using this new package directly. --- framework/core/composer.json | 1 + framework/core/src/Http/Server.php | 27 ++++++++++++++++++--------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/framework/core/composer.json b/framework/core/composer.json index 1ba12df12..f0f3ce9ff 100644 --- a/framework/core/composer.json +++ b/framework/core/composer.json @@ -58,6 +58,7 @@ "symfony/yaml": "^3.3", "tobscure/json-api": "^0.3.0", "zendframework/zend-diactoros": "^1.8.4", + "zendframework/zend-httphandlerrunner": "^1.0", "zendframework/zend-stratigility": "^3.0" }, "require-dev": { diff --git a/framework/core/src/Http/Server.php b/framework/core/src/Http/Server.php index 825dc4bce..6c24afba6 100644 --- a/framework/core/src/Http/Server.php +++ b/framework/core/src/Http/Server.php @@ -12,7 +12,13 @@ namespace Flarum\Http; use Psr\Http\Server\RequestHandlerInterface; -use Zend\Diactoros\Server as DiactorosServer; +use Throwable; +use Zend\Diactoros\Response; +use Zend\Diactoros\ServerRequest; +use Zend\Diactoros\ServerRequestFactory; +use Zend\HttpHandlerRunner\Emitter\SapiEmitter; +use Zend\HttpHandlerRunner\RequestHandlerRunner; +use Zend\Stratigility\Middleware\ErrorResponseGenerator; class Server { @@ -25,13 +31,16 @@ class Server public function listen() { - DiactorosServer::createServer( - [$this->requestHandler, 'handle'], - $_SERVER, - $_GET, - $_POST, - $_COOKIE, - $_FILES - )->listen(); + $runner = new RequestHandlerRunner( + $this->requestHandler, + new SapiEmitter, + [ServerRequestFactory::class, 'fromGlobals'], + function (Throwable $e) { + $generator = new ErrorResponseGenerator; + + return $generator($e, new ServerRequest, new Response); + } + ); + $runner->run(); } } From 98aaa6a130479571d2ac2112c67631d2a76875b7 Mon Sep 17 00:00:00 2001 From: Franz Liedke Date: Fri, 3 Aug 2018 00:38:44 +0200 Subject: [PATCH 06/13] One BusServiceProvider is enough --- framework/core/src/Bus/BusServiceProvider.php | 14 ++++++++++++-- framework/core/src/Foundation/InstalledSite.php | 4 +--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/framework/core/src/Bus/BusServiceProvider.php b/framework/core/src/Bus/BusServiceProvider.php index c07b9b812..938da0c55 100644 --- a/framework/core/src/Bus/BusServiceProvider.php +++ b/framework/core/src/Bus/BusServiceProvider.php @@ -12,17 +12,27 @@ namespace Flarum\Bus; use Flarum\Foundation\AbstractServiceProvider; -use Illuminate\Contracts\Bus\Dispatcher as BusContract; +use Illuminate\Bus\Dispatcher as BaseDispatcher; +use Illuminate\Contracts\Bus\Dispatcher as DispatcherContract; +use Illuminate\Contracts\Bus\QueueingDispatcher as QueueingDispatcherContract; use Illuminate\Contracts\Queue\Factory as QueueFactoryContract; class BusServiceProvider extends AbstractServiceProvider { public function register() { - $this->app->bind(BusContract::class, function ($app) { + $this->app->bind(BaseDispatcher::class, function ($app) { return new Dispatcher($app, function ($connection = null) use ($app) { return $app[QueueFactoryContract::class]->connection($connection); }); }); + + $this->app->alias( + BaseDispatcher::class, DispatcherContract::class + ); + + $this->app->alias( + BaseDispatcher::class, QueueingDispatcherContract::class + ); } } diff --git a/framework/core/src/Foundation/InstalledSite.php b/framework/core/src/Foundation/InstalledSite.php index dd6740a30..f30d29625 100644 --- a/framework/core/src/Foundation/InstalledSite.php +++ b/framework/core/src/Foundation/InstalledSite.php @@ -13,7 +13,7 @@ namespace Flarum\Foundation; use Flarum\Admin\AdminServiceProvider; use Flarum\Api\ApiServiceProvider; -use Flarum\Bus\BusServiceProvider as BusProvider; +use Flarum\Bus\BusServiceProvider; use Flarum\Database\DatabaseServiceProvider; use Flarum\Database\MigrationServiceProvider; use Flarum\Discussion\DiscussionServiceProvider; @@ -125,8 +125,6 @@ class InstalledSite implements SiteInterface $laravel->register(ViewServiceProvider::class); $laravel->register(ValidationServiceProvider::class); - $laravel->register(BusProvider::class); - $settings = $laravel->make(SettingsRepositoryInterface::class); $config->set('mail.driver', $settings->get('mail_driver')); From e65b3ac153915504b47c6765e4af5bfb3328c946 Mon Sep 17 00:00:00 2001 From: Franz Liedke Date: Tue, 14 Aug 2018 23:30:49 +0200 Subject: [PATCH 07/13] Split SessionServiceProvider from UserServiceProvider This lets us register the former during installation, where the latter is not yet registered. That, in turn, means we can finally re-enable the StartSession middleware in the installer app, which we need to log in the new admin user when installation is complete. --- .../core/src/Foundation/InstalledSite.php | 2 ++ .../core/src/Foundation/UninstalledSite.php | 2 ++ framework/core/src/Install/Installer.php | 2 +- .../core/src/User/SessionServiceProvider.php | 35 +++++++++++++++++++ .../core/src/User/UserServiceProvider.php | 16 --------- .../tests/Test/Concerns/MakesApiRequests.php | 2 ++ 6 files changed, 42 insertions(+), 17 deletions(-) create mode 100644 framework/core/src/User/SessionServiceProvider.php diff --git a/framework/core/src/Foundation/InstalledSite.php b/framework/core/src/Foundation/InstalledSite.php index f30d29625..c811e32cf 100644 --- a/framework/core/src/Foundation/InstalledSite.php +++ b/framework/core/src/Foundation/InstalledSite.php @@ -29,6 +29,7 @@ use Flarum\Search\SearchServiceProvider; use Flarum\Settings\SettingsRepositoryInterface; use Flarum\Settings\SettingsServiceProvider; use Flarum\Update\UpdateServiceProvider; +use Flarum\User\SessionServiceProvider; use Flarum\User\UserServiceProvider; use Illuminate\Cache\FileStore; use Illuminate\Cache\Repository as CacheRepository; @@ -143,6 +144,7 @@ class InstalledSite implements SiteInterface $laravel->register(NotificationServiceProvider::class); $laravel->register(PostServiceProvider::class); $laravel->register(SearchServiceProvider::class); + $laravel->register(SessionServiceProvider::class); $laravel->register(UserServiceProvider::class); $laravel->register(UpdateServiceProvider::class); diff --git a/framework/core/src/Foundation/UninstalledSite.php b/framework/core/src/Foundation/UninstalledSite.php index b655e150d..9519d25f2 100644 --- a/framework/core/src/Foundation/UninstalledSite.php +++ b/framework/core/src/Foundation/UninstalledSite.php @@ -16,6 +16,7 @@ use Flarum\Install\InstallServiceProvider; use Flarum\Locale\LocaleServiceProvider; use Flarum\Settings\SettingsRepositoryInterface; use Flarum\Settings\UninstalledSettingsRepository; +use Flarum\User\SessionServiceProvider; use Illuminate\Config\Repository as ConfigRepository; use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Filesystem\FilesystemServiceProvider; @@ -79,6 +80,7 @@ class UninstalledSite implements SiteInterface $laravel->register(LocaleServiceProvider::class); $laravel->register(FilesystemServiceProvider::class); + $laravel->register(SessionServiceProvider::class); $laravel->register(ValidationServiceProvider::class); $laravel->register(InstallServiceProvider::class); diff --git a/framework/core/src/Install/Installer.php b/framework/core/src/Install/Installer.php index 0ca0253e7..da9889db4 100644 --- a/framework/core/src/Install/Installer.php +++ b/framework/core/src/Install/Installer.php @@ -38,7 +38,7 @@ class Installer implements AppInterface { $pipe = new MiddlewarePipe; $pipe->pipe($this->laravel->make(HandleErrorsWithWhoops::class)); - #$pipe->pipe($this->laravel->make(StartSession::class)); + $pipe->pipe($this->laravel->make(StartSession::class)); $pipe->pipe( $this->laravel->make( DispatchRoute::class, diff --git a/framework/core/src/User/SessionServiceProvider.php b/framework/core/src/User/SessionServiceProvider.php new file mode 100644 index 000000000..87004fe20 --- /dev/null +++ b/framework/core/src/User/SessionServiceProvider.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Flarum\User; + +use Flarum\Foundation\AbstractServiceProvider; +use Illuminate\Session\FileSessionHandler; +use SessionHandlerInterface; + +class SessionServiceProvider extends AbstractServiceProvider +{ + /** + * {@inheritdoc} + */ + public function register() + { + $this->app->singleton('session.handler', function ($app) { + return new FileSessionHandler( + $app['files'], + $app['config']['session.files'], + $app['config']['session.lifetime'] + ); + }); + + $this->app->alias('session.handler', SessionHandlerInterface::class); + } +} diff --git a/framework/core/src/User/UserServiceProvider.php b/framework/core/src/User/UserServiceProvider.php index a52d128ad..12aae50fb 100644 --- a/framework/core/src/User/UserServiceProvider.php +++ b/framework/core/src/User/UserServiceProvider.php @@ -15,9 +15,7 @@ use Flarum\Event\ConfigureUserPreferences; use Flarum\Event\GetPermission; use Flarum\Foundation\AbstractServiceProvider; use Illuminate\Contracts\Container\Container; -use Illuminate\Session\FileSessionHandler; use RuntimeException; -use SessionHandlerInterface; class UserServiceProvider extends AbstractServiceProvider { @@ -26,24 +24,10 @@ class UserServiceProvider extends AbstractServiceProvider */ public function register() { - $this->registerSession(); $this->registerGate(); $this->registerAvatarsFilesystem(); } - protected function registerSession() - { - $this->app->singleton('session.handler', function ($app) { - return new FileSessionHandler( - $app['files'], - $app['config']['session.files'], - $app['config']['session.lifetime'] - ); - }); - - $this->app->alias('session.handler', SessionHandlerInterface::class); - } - protected function registerGate() { $this->app->singleton('flarum.gate', function ($app) { diff --git a/framework/core/tests/Test/Concerns/MakesApiRequests.php b/framework/core/tests/Test/Concerns/MakesApiRequests.php index bcf69f819..d963e9af0 100644 --- a/framework/core/tests/Test/Concerns/MakesApiRequests.php +++ b/framework/core/tests/Test/Concerns/MakesApiRequests.php @@ -14,6 +14,7 @@ namespace Flarum\Tests\Test\Concerns; use Flarum\Api\ApiServiceProvider; use Flarum\Api\Client; use Flarum\User\Guest; +use Flarum\User\SessionServiceProvider; use Flarum\User\User; use Flarum\User\UserServiceProvider; use Psr\Http\Message\ResponseInterface; @@ -22,6 +23,7 @@ trait MakesApiRequests { public function call(string $controller, User $actor = null, array $queryParams = [], array $body = []): ResponseInterface { + $this->app->register(SessionServiceProvider::class); $this->app->register(UserServiceProvider::class); $this->app->register(ApiServiceProvider::class); $this->app->make('flarum.api.middleware'); From eb0e50a305d150806f020dc41ad03b71de20cf42 Mon Sep 17 00:00:00 2001 From: Franz Liedke Date: Wed, 15 Aug 2018 00:47:00 +0200 Subject: [PATCH 08/13] Fix tests after sites refactoring --- .../Controller/CreateUserControllerTest.php | 2 +- .../DefaultInstallationCommandTest.php | 24 ++- .../core/tests/Test/Concerns/CreatesForum.php | 144 ++++++++++-------- .../tests/Test/Concerns/MakesApiRequests.php | 9 +- 4 files changed, 95 insertions(+), 84 deletions(-) diff --git a/framework/core/tests/Api/Controller/CreateUserControllerTest.php b/framework/core/tests/Api/Controller/CreateUserControllerTest.php index 285b2a53a..dd51dc0da 100644 --- a/framework/core/tests/Api/Controller/CreateUserControllerTest.php +++ b/framework/core/tests/Api/Controller/CreateUserControllerTest.php @@ -84,7 +84,7 @@ class CreateUserControllerTest extends ApiControllerTestCase public function disabling_sign_up_prevents_user_creation() { /** @var SettingsRepositoryInterface $settings */ - $settings = $this->app->make(SettingsRepositoryInterface::class); + $settings = app(SettingsRepositoryInterface::class); $settings->set('allow_sign_up', false); try { diff --git a/framework/core/tests/Install/DefaultInstallationCommandTest.php b/framework/core/tests/Install/DefaultInstallationCommandTest.php index 7a8014fc0..0d2fee829 100644 --- a/framework/core/tests/Install/DefaultInstallationCommandTest.php +++ b/framework/core/tests/Install/DefaultInstallationCommandTest.php @@ -12,9 +12,8 @@ namespace Flarum\Tests\Install; use Flarum\Install\Console\InstallCommand; -use Flarum\Install\InstallServiceProvider; use Flarum\Tests\Test\TestCase; -use Flarum\User\User; +use Illuminate\Database\Connectors\ConnectionFactory; use Symfony\Component\Console\Input\StringInput; use Symfony\Component\Console\Output\StreamOutput; @@ -27,13 +26,12 @@ class DefaultInstallationCommandTest extends TestCase */ public function allows_forum_installation() { - if (file_exists($this->app->basePath().DIRECTORY_SEPARATOR.'config.php')) { - unlink($this->app->basePath().DIRECTORY_SEPARATOR.'config.php'); + if (file_exists(base_path('config.php'))) { + unlink(base_path('config.php')); } - $this->app->register(InstallServiceProvider::class); /** @var InstallCommand $command */ - $command = $this->app->make(InstallCommand::class); + $command = app(InstallCommand::class); $command->setDataSource($this->configuration); $body = fopen('php://temp', 'wb+'); @@ -42,10 +40,20 @@ class DefaultInstallationCommandTest extends TestCase $command->run($input, $output); - $this->assertFileExists($this->app->basePath().DIRECTORY_SEPARATOR.'config.php'); + $this->assertFileExists(base_path('config.php')); $admin = $this->configuration->getAdminUser(); - $this->assertEquals(User::find(1)->username, $admin['username']); + $this->assertEquals( + $this->getDatabase()->table('users')->find(1)->username, + $admin['username'] + ); + } + + private function getDatabase() + { + $factory = new ConnectionFactory(app()); + + return $factory->make($this->configuration->getDatabaseConfiguration()); } } diff --git a/framework/core/tests/Test/Concerns/CreatesForum.php b/framework/core/tests/Test/Concerns/CreatesForum.php index ac4029731..9476dcfe6 100644 --- a/framework/core/tests/Test/Concerns/CreatesForum.php +++ b/framework/core/tests/Test/Concerns/CreatesForum.php @@ -11,14 +11,17 @@ namespace Flarum\Tests\Test\Concerns; +use Flarum\Database\DatabaseMigrationRepository; use Flarum\Database\Migrator; use Flarum\Foundation\Application; -use Flarum\Foundation\Site; +use Flarum\Foundation\InstalledSite; +use Flarum\Foundation\SiteInterface; +use Flarum\Foundation\UninstalledSite; use Flarum\Http\Server; use Flarum\Install\Console\DataProviderInterface; use Flarum\Install\Console\DefaultsDataProvider; use Illuminate\Database\ConnectionInterface; -use Illuminate\Database\Schema\Builder; +use Illuminate\Database\Connectors\ConnectionFactory; trait CreatesForum { @@ -28,7 +31,7 @@ trait CreatesForum protected $http; /** - * @var Site + * @var SiteInterface */ protected $site; @@ -51,41 +54,51 @@ trait CreatesForum protected function createsSite() { - $this->site = (new Site) - ->setBasePath(__DIR__.'/../../tmp') - ->setPublicPath(__DIR__.'/../../tmp/public'); + if ($this->isInstalled) { + $this->site = new InstalledSite( + __DIR__.'/../../tmp', + __DIR__.'/../../tmp/public', + $this->getFlarumConfig() + ); + } else { + $this->site = new UninstalledSite( + __DIR__.'/../../tmp', + __DIR__.'/../../tmp/public' + ); + } } protected function createsHttpForum() { - $this->http = Server::fromSite( - $this->site - ); + $this->app = $this->site->bootApp(); - $this->app = $this->http->app; + $this->http = new Server( + $this->app->getRequestHandler() + ); } - protected function refreshApplication() + protected function collectsConfiguration() { - $this->createsSite(); + $this->configuration = new DefaultsDataProvider(); - $data = new DefaultsDataProvider(); + $this->configuration->setDebugMode(); + $this->configuration->setSetting('mail_driver', 'log'); - $data->setDebugMode(); - $data->setSetting('mail_driver', 'log'); - - $database = $data->getDatabaseConfiguration(); + $database = $this->configuration->getDatabaseConfiguration(); $database['host'] = env('DB_HOST', $database['host']); $database['database'] = env('DB_DATABASE', $database['database']); $database['username'] = env('DB_USERNAME', $database['username']); $database['password'] = env('DB_PASSWORD', $database['password']); - $data->setDatabaseConfiguration($database); + $this->configuration->setDatabaseConfiguration($database); + } - $this->configuration = $data; + protected function refreshApplication() + { + $this->collectsConfiguration(); - $this->setsApplicationConfiguration($data); + $this->seedsDatabase(); - $this->seedsApplication(); + $this->createsSite(); $this->createsHttpForum(); } @@ -93,59 +106,56 @@ trait CreatesForum protected function teardownApplication() { /** @var ConnectionInterface $connection */ - $connection = $this->app->make(ConnectionInterface::class); + $connection = app(ConnectionInterface::class); $connection->rollBack(); } - protected function setsApplicationConfiguration(DataProviderInterface $data) + protected function getFlarumConfig() { - if ($this->isInstalled) { - $dbConfig = $data->getDatabaseConfiguration(); - $this->site->setConfig( - $config = [ - 'debug' => $data->isDebugMode(), - 'database' => [ - 'driver' => $dbConfig['driver'], - 'host' => $dbConfig['host'], - 'database' => $dbConfig['database'], - 'username' => $dbConfig['username'], - 'password' => $dbConfig['password'], - 'charset' => 'utf8mb4', - 'collation' => 'utf8mb4_unicode_ci', - 'prefix' => $dbConfig['prefix'], - 'port' => $dbConfig['port'], - 'strict' => false - ], - 'url' => $data->getBaseUrl(), - 'paths' => [ - 'api' => 'api', - 'admin' => 'admin', - ], - ] - ); - } + $dbConfig = $this->configuration->getDatabaseConfiguration(); + + return [ + 'debug' => $this->configuration->isDebugMode(), + 'database' => [ + 'driver' => $dbConfig['driver'], + 'host' => $dbConfig['host'], + 'database' => $dbConfig['database'], + 'username' => $dbConfig['username'], + 'password' => $dbConfig['password'], + 'charset' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'prefix' => $dbConfig['prefix'], + 'port' => $dbConfig['port'], + 'strict' => false + ], + 'url' => $this->configuration->getBaseUrl(), + 'paths' => [ + 'api' => 'api', + 'admin' => 'admin', + ], + ]; } - protected function seedsApplication() + protected function seedsDatabase() { - if ($this->isInstalled) { - $app = app(\Illuminate\Contracts\Foundation\Application::class); - - $app->bind(Builder::class, function ($container) { - return $container->make(ConnectionInterface::class)->getSchemaBuilder(); - }); - - /** @var Migrator $migrator */ - $migrator = $app->make(Migrator::class); - if (! $migrator->getRepository()->repositoryExists()) { - $migrator->getRepository()->createRepository(); - } - - $migrator->run(__DIR__.'/../../../migrations'); - - /** @var ConnectionInterface $connection */ - $connection = $app->make(\Illuminate\Database\ConnectionInterface::class); - $connection->beginTransaction(); + if (! $this->isInstalled) { + return; } + + $app = app(\Illuminate\Contracts\Foundation\Application::class); + + $factory = new ConnectionFactory($app); + $db = $factory->make($this->configuration->getDatabaseConfiguration()); + + $repository = new DatabaseMigrationRepository($db, 'migrations'); + $migrator = new Migrator($repository, $db, app('files')); + + if (! $migrator->getRepository()->repositoryExists()) { + $migrator->getRepository()->createRepository(); + } + + $migrator->run(__DIR__.'/../../../migrations'); + + $db->beginTransaction(); } } diff --git a/framework/core/tests/Test/Concerns/MakesApiRequests.php b/framework/core/tests/Test/Concerns/MakesApiRequests.php index d963e9af0..3e039ad49 100644 --- a/framework/core/tests/Test/Concerns/MakesApiRequests.php +++ b/framework/core/tests/Test/Concerns/MakesApiRequests.php @@ -11,24 +11,17 @@ namespace Flarum\Tests\Test\Concerns; -use Flarum\Api\ApiServiceProvider; use Flarum\Api\Client; use Flarum\User\Guest; -use Flarum\User\SessionServiceProvider; use Flarum\User\User; -use Flarum\User\UserServiceProvider; use Psr\Http\Message\ResponseInterface; trait MakesApiRequests { public function call(string $controller, User $actor = null, array $queryParams = [], array $body = []): ResponseInterface { - $this->app->register(SessionServiceProvider::class); - $this->app->register(UserServiceProvider::class); - $this->app->register(ApiServiceProvider::class); - $this->app->make('flarum.api.middleware'); /** @var Client $api */ - $api = $this->app->make(Client::class); + $api = app(Client::class); return $api->send($controller, $actor ?? new Guest, $queryParams, $body); } From 72ba76b825857655bfb8f67c3ce32c153a71d027 Mon Sep 17 00:00:00 2001 From: Franz Liedke Date: Tue, 21 Aug 2018 23:42:00 +0200 Subject: [PATCH 09/13] Instantiate DispatchRoute manually Since we are already providing the first and only argument manually, we might as well instantiate the object manually. Same effect, same coupling, less code. --- framework/core/src/Admin/AdminServiceProvider.php | 2 +- framework/core/src/Api/ApiServiceProvider.php | 2 +- framework/core/src/Forum/ForumServiceProvider.php | 2 +- framework/core/src/Foundation/InstalledApp.php | 5 +---- framework/core/src/Install/Installer.php | 5 +---- 5 files changed, 5 insertions(+), 11 deletions(-) diff --git a/framework/core/src/Admin/AdminServiceProvider.php b/framework/core/src/Admin/AdminServiceProvider.php index 6ec339bd2..859b0db0c 100644 --- a/framework/core/src/Admin/AdminServiceProvider.php +++ b/framework/core/src/Admin/AdminServiceProvider.php @@ -62,7 +62,7 @@ class AdminServiceProvider extends AbstractServiceProvider event(new ConfigureMiddleware($pipe, 'admin')); - $pipe->pipe($app->make(DispatchRoute::class, ['routes' => $app->make('flarum.admin.routes')])); + $pipe->pipe(new DispatchRoute($app->make('flarum.admin.routes'))); return $pipe; }); diff --git a/framework/core/src/Api/ApiServiceProvider.php b/framework/core/src/Api/ApiServiceProvider.php index b5b83d24e..f9370176b 100644 --- a/framework/core/src/Api/ApiServiceProvider.php +++ b/framework/core/src/Api/ApiServiceProvider.php @@ -66,7 +66,7 @@ class ApiServiceProvider extends AbstractServiceProvider event(new ConfigureMiddleware($pipe, 'api')); - $pipe->pipe($app->make(DispatchRoute::class, ['routes' => $app->make('flarum.api.routes')])); + $pipe->pipe(new DispatchRoute($app->make('flarum.api.routes'))); return $pipe; }); diff --git a/framework/core/src/Forum/ForumServiceProvider.php b/framework/core/src/Forum/ForumServiceProvider.php index 62655ef2a..c08d1deab 100644 --- a/framework/core/src/Forum/ForumServiceProvider.php +++ b/framework/core/src/Forum/ForumServiceProvider.php @@ -66,7 +66,7 @@ class ForumServiceProvider extends AbstractServiceProvider event(new ConfigureMiddleware($pipe, 'forum')); - $pipe->pipe($app->make(DispatchRoute::class, ['routes' => $app->make('flarum.forum.routes')])); + $pipe->pipe(new DispatchRoute($app->make('flarum.forum.routes'))); return $pipe; }); diff --git a/framework/core/src/Foundation/InstalledApp.php b/framework/core/src/Foundation/InstalledApp.php index 1b0c574bb..aefae6f02 100644 --- a/framework/core/src/Foundation/InstalledApp.php +++ b/framework/core/src/Foundation/InstalledApp.php @@ -99,10 +99,7 @@ class InstalledApp implements AppInterface { $pipe = new MiddlewarePipe; $pipe->pipe( - $this->laravel->make( - DispatchRoute::class, - ['routes' => $this->laravel->make('flarum.update.routes')] - ) + new DispatchRoute($this->laravel->make('flarum.update.routes')) ); return $pipe; diff --git a/framework/core/src/Install/Installer.php b/framework/core/src/Install/Installer.php index da9889db4..16ea3b9b5 100644 --- a/framework/core/src/Install/Installer.php +++ b/framework/core/src/Install/Installer.php @@ -40,10 +40,7 @@ class Installer implements AppInterface $pipe->pipe($this->laravel->make(HandleErrorsWithWhoops::class)); $pipe->pipe($this->laravel->make(StartSession::class)); $pipe->pipe( - $this->laravel->make( - DispatchRoute::class, - ['routes' => $this->laravel->make('flarum.install.routes')] - ) + new DispatchRoute($this->laravel->make('flarum.install.routes')) ); return $pipe; From 554322f0b860c86333e5782a6a56dcb0d57d17a4 Mon Sep 17 00:00:00 2001 From: Franz Liedke Date: Tue, 21 Aug 2018 23:46:02 +0200 Subject: [PATCH 10/13] Use more honest method names --- framework/core/src/Foundation/InstalledApp.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/framework/core/src/Foundation/InstalledApp.php b/framework/core/src/Foundation/InstalledApp.php index aefae6f02..a8cabf9d4 100644 --- a/framework/core/src/Foundation/InstalledApp.php +++ b/framework/core/src/Foundation/InstalledApp.php @@ -47,9 +47,9 @@ class InstalledApp implements AppInterface public function getRequestHandler() { if ($this->inMaintenanceMode()) { - return $this->getMaintenanceMiddleware(); + return $this->getMaintenanceHandler(); } elseif ($this->needsUpdate()) { - return $this->getUpdaterMiddleware(); + return $this->getUpdaterHandler(); } $pipe = new MiddlewarePipe; @@ -69,7 +69,7 @@ class InstalledApp implements AppInterface /** * @return \Psr\Http\Server\RequestHandlerInterface */ - private function getMaintenanceMiddleware() + private function getMaintenanceHandler() { $pipe = new MiddlewarePipe; @@ -95,7 +95,7 @@ class InstalledApp implements AppInterface /** * @return \Psr\Http\Server\RequestHandlerInterface */ - public function getUpdaterMiddleware() + public function getUpdaterHandler() { $pipe = new MiddlewarePipe; $pipe->pipe( From 02f27b0fa15ddda95ed751d96a839ce01582865b Mon Sep 17 00:00:00 2001 From: Franz Liedke Date: Wed, 22 Aug 2018 00:07:50 +0200 Subject: [PATCH 11/13] Implement request handler for maintenance mode --- .../core/src/Foundation/InstalledApp.php | 22 +------ .../src/Foundation/MaintenanceModeHandler.php | 58 +++++++++++++++++++ 2 files changed, 59 insertions(+), 21 deletions(-) create mode 100644 framework/core/src/Foundation/MaintenanceModeHandler.php diff --git a/framework/core/src/Foundation/InstalledApp.php b/framework/core/src/Foundation/InstalledApp.php index a8cabf9d4..9149f3806 100644 --- a/framework/core/src/Foundation/InstalledApp.php +++ b/framework/core/src/Foundation/InstalledApp.php @@ -18,9 +18,7 @@ use Flarum\Foundation\Console\CacheClearCommand; use Flarum\Foundation\Console\InfoCommand; use Flarum\Http\Middleware\DispatchRoute; use Flarum\Settings\SettingsRepositoryInterface; -use Zend\Diactoros\Response\HtmlResponse; use Zend\Stratigility\MiddlewarePipe; -use function Zend\Stratigility\middleware; use function Zend\Stratigility\path; class InstalledApp implements AppInterface @@ -47,7 +45,7 @@ class InstalledApp implements AppInterface public function getRequestHandler() { if ($this->inMaintenanceMode()) { - return $this->getMaintenanceHandler(); + return new MaintenanceModeHandler(); } elseif ($this->needsUpdate()) { return $this->getUpdaterHandler(); } @@ -66,24 +64,6 @@ class InstalledApp implements AppInterface return $this->config['offline'] ?? false; } - /** - * @return \Psr\Http\Server\RequestHandlerInterface - */ - private function getMaintenanceHandler() - { - $pipe = new MiddlewarePipe; - - $pipe->pipe(middleware(function () { - // FIXME: Fix path to 503.html - // TODO: FOR API render JSON-API error document for HTTP 503 - return new HtmlResponse( - file_get_contents(__DIR__.'/../../503.html'), 503 - ); - })); - - return $pipe; - } - private function needsUpdate(): bool { $settings = $this->laravel->make(SettingsRepositoryInterface::class); diff --git a/framework/core/src/Foundation/MaintenanceModeHandler.php b/framework/core/src/Foundation/MaintenanceModeHandler.php new file mode 100644 index 000000000..554ac002a --- /dev/null +++ b/framework/core/src/Foundation/MaintenanceModeHandler.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Flarum\Foundation; + +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Server\RequestHandlerInterface; +use Tobscure\JsonApi\Document; +use Zend\Diactoros\Response\HtmlResponse; +use Zend\Diactoros\Response\JsonResponse; + +class MaintenanceModeHandler implements RequestHandlerInterface +{ + const MESSAGE = 'Currently down for maintenance. Please come back later.'; + + /** + * Handle the request and return a response. + */ + public function handle(ServerRequestInterface $request): ResponseInterface + { + // Special handling for API requests: they get a proper API response + if ($this->isApiRequest($request)) { + return $this->apiResponse(); + } + + // By default, return a simple text message. + return new HtmlResponse(self::MESSAGE, 503); + } + + private function isApiRequest(ServerRequestInterface $request): bool + { + return str_contains( + $request->getHeaderLine('Accept'), + 'application/vnd.api+json' + ); + } + + private function apiResponse(): ResponseInterface + { + return new JsonResponse( + (new Document)->setErrors([ + 'status' => '503', + 'title' => self::MESSAGE + ]), + 503, + ['Content-Type' => 'application/vnd.api+json'] + ); + } +} From 31480dd1ed60150469eb97643afc539839aa45a3 Mon Sep 17 00:00:00 2001 From: Franz Liedke Date: Wed, 22 Aug 2018 09:17:43 +0200 Subject: [PATCH 12/13] Load middleware stacks lazily This way, the forum middleware does not need to be loaded for API requests, and vice-versa. --- framework/core/composer.json | 2 ++ .../core/src/Foundation/InstalledApp.php | 21 +++++++++++-------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/framework/core/composer.json b/framework/core/composer.json index f0f3ce9ff..a2fdf8000 100644 --- a/framework/core/composer.json +++ b/framework/core/composer.json @@ -44,6 +44,8 @@ "league/flysystem": "^1.0.11", "league/oauth2-client": "~1.0", "matthiasmullie/minify": "^1.3", + "middlewares/base-path-router": "^0.2.1", + "middlewares/request-handler": "^1.2", "monolog/monolog": "^1.16.0", "nikic/fast-route": "^0.6", "oyejorge/less.php": "^1.7", diff --git a/framework/core/src/Foundation/InstalledApp.php b/framework/core/src/Foundation/InstalledApp.php index 9149f3806..d5a94089a 100644 --- a/framework/core/src/Foundation/InstalledApp.php +++ b/framework/core/src/Foundation/InstalledApp.php @@ -18,8 +18,9 @@ use Flarum\Foundation\Console\CacheClearCommand; use Flarum\Foundation\Console\InfoCommand; use Flarum\Http\Middleware\DispatchRoute; use Flarum\Settings\SettingsRepositoryInterface; +use Middlewares\BasePathRouter; +use Middlewares\RequestHandler; use Zend\Stratigility\MiddlewarePipe; -use function Zend\Stratigility\path; class InstalledApp implements AppInterface { @@ -52,9 +53,14 @@ class InstalledApp implements AppInterface $pipe = new MiddlewarePipe; - $pipe->pipe($this->subPath('api', 'flarum.api.middleware')); - $pipe->pipe($this->subPath('admin', 'flarum.admin.middleware')); - $pipe->pipe($this->subPath('', 'flarum.forum.middleware')); + $pipe->pipe( + new BasePathRouter([ + $this->subPath('api') => 'flarum.api.middleware', + $this->subPath('admin') => 'flarum.admin.middleware', + $this->subPath('') => 'flarum.forum.middleware', + ]) + ); + $pipe->pipe(new RequestHandler($this->laravel)); return $pipe; } @@ -85,12 +91,9 @@ class InstalledApp implements AppInterface return $pipe; } - private function subPath($pathName, $middlewareStack) + private function subPath($pathName): string { - return path( - parse_url($this->laravel->url($pathName), PHP_URL_PATH) ?: '/', - $this->laravel->make($middlewareStack) - ); + return parse_url($this->laravel->url($pathName), PHP_URL_PATH) ?: '/'; } /** From e2b890152b67ac8df31c5044e75eca83136619ba Mon Sep 17 00:00:00 2001 From: Franz Liedke Date: Thu, 23 Aug 2018 22:17:37 +0200 Subject: [PATCH 13/13] Pass container into apps, adapt path matching --- framework/core/composer.json | 1 + .../core/src/Foundation/InstalledApp.php | 36 +++++++++++-------- framework/core/src/Install/Installer.php | 18 +++++----- 3 files changed, 32 insertions(+), 23 deletions(-) diff --git a/framework/core/composer.json b/framework/core/composer.json index a2fdf8000..ff06ace99 100644 --- a/framework/core/composer.json +++ b/framework/core/composer.json @@ -44,6 +44,7 @@ "league/flysystem": "^1.0.11", "league/oauth2-client": "~1.0", "matthiasmullie/minify": "^1.3", + "middlewares/base-path": "^1.1", "middlewares/base-path-router": "^0.2.1", "middlewares/request-handler": "^1.2", "monolog/monolog": "^1.16.0", diff --git a/framework/core/src/Foundation/InstalledApp.php b/framework/core/src/Foundation/InstalledApp.php index d5a94089a..e25c30e1a 100644 --- a/framework/core/src/Foundation/InstalledApp.php +++ b/framework/core/src/Foundation/InstalledApp.php @@ -18,6 +18,8 @@ use Flarum\Foundation\Console\CacheClearCommand; use Flarum\Foundation\Console\InfoCommand; use Flarum\Http\Middleware\DispatchRoute; use Flarum\Settings\SettingsRepositoryInterface; +use Illuminate\Contracts\Container\Container; +use Middlewares\BasePath; use Middlewares\BasePathRouter; use Middlewares\RequestHandler; use Zend\Stratigility\MiddlewarePipe; @@ -25,18 +27,18 @@ use Zend\Stratigility\MiddlewarePipe; class InstalledApp implements AppInterface { /** - * @var Application + * @var Container */ - protected $laravel; + protected $container; /** * @var array */ protected $config; - public function __construct(Application $laravel, array $config) + public function __construct(Container $container, array $config) { - $this->laravel = $laravel; + $this->container = $container; $this->config = $config; } @@ -53,14 +55,15 @@ class InstalledApp implements AppInterface $pipe = new MiddlewarePipe; + $pipe->pipe(new BasePath($this->basePath())); $pipe->pipe( new BasePathRouter([ $this->subPath('api') => 'flarum.api.middleware', $this->subPath('admin') => 'flarum.admin.middleware', - $this->subPath('') => 'flarum.forum.middleware', + '/' => 'flarum.forum.middleware', ]) ); - $pipe->pipe(new RequestHandler($this->laravel)); + $pipe->pipe(new RequestHandler($this->container)); return $pipe; } @@ -72,7 +75,7 @@ class InstalledApp implements AppInterface private function needsUpdate(): bool { - $settings = $this->laravel->make(SettingsRepositoryInterface::class); + $settings = $this->container->make(SettingsRepositoryInterface::class); $version = $settings->get('version'); return $version !== Application::VERSION; @@ -85,15 +88,20 @@ class InstalledApp implements AppInterface { $pipe = new MiddlewarePipe; $pipe->pipe( - new DispatchRoute($this->laravel->make('flarum.update.routes')) + new DispatchRoute($this->container->make('flarum.update.routes')) ); return $pipe; } + private function basePath(): string + { + return parse_url($this->config['url'], PHP_URL_PATH) ?: '/'; + } + private function subPath($pathName): string { - return parse_url($this->laravel->url($pathName), PHP_URL_PATH) ?: '/'; + return '/'.$this->config['paths'][$pathName]; } /** @@ -102,11 +110,11 @@ class InstalledApp implements AppInterface public function getConsoleCommands() { return [ - $this->laravel->make(GenerateMigrationCommand::class), - $this->laravel->make(InfoCommand::class, ['config' => $this->config]), - $this->laravel->make(MigrateCommand::class), - $this->laravel->make(ResetCommand::class), - $this->laravel->make(CacheClearCommand::class), + $this->container->make(GenerateMigrationCommand::class), + $this->container->make(InfoCommand::class, ['config' => $this->config]), + $this->container->make(MigrateCommand::class), + $this->container->make(ResetCommand::class), + $this->container->make(CacheClearCommand::class), ]; } } diff --git a/framework/core/src/Install/Installer.php b/framework/core/src/Install/Installer.php index 16ea3b9b5..3df6a1b86 100644 --- a/framework/core/src/Install/Installer.php +++ b/framework/core/src/Install/Installer.php @@ -12,23 +12,23 @@ namespace Flarum\Install; use Flarum\Foundation\AppInterface; -use Flarum\Foundation\Application; use Flarum\Http\Middleware\DispatchRoute; use Flarum\Http\Middleware\HandleErrorsWithWhoops; use Flarum\Http\Middleware\StartSession; use Flarum\Install\Console\InstallCommand; +use Illuminate\Contracts\Container\Container; use Zend\Stratigility\MiddlewarePipe; class Installer implements AppInterface { /** - * @var Application + * @var Container */ - protected $laravel; + protected $container; - public function __construct(Application $laravel) + public function __construct(Container $container) { - $this->laravel = $laravel; + $this->container = $container; } /** @@ -37,10 +37,10 @@ class Installer implements AppInterface public function getRequestHandler() { $pipe = new MiddlewarePipe; - $pipe->pipe($this->laravel->make(HandleErrorsWithWhoops::class)); - $pipe->pipe($this->laravel->make(StartSession::class)); + $pipe->pipe($this->container->make(HandleErrorsWithWhoops::class)); + $pipe->pipe($this->container->make(StartSession::class)); $pipe->pipe( - new DispatchRoute($this->laravel->make('flarum.install.routes')) + new DispatchRoute($this->container->make('flarum.install.routes')) ); return $pipe; @@ -52,7 +52,7 @@ class Installer implements AppInterface public function getConsoleCommands() { return [ - $this->laravel->make(InstallCommand::class), + $this->container->make(InstallCommand::class), ]; } }