From fe07d4064be08b8e001f184f4b74692daf487da6 Mon Sep 17 00:00:00 2001 From: Franz Liedke Date: Mon, 16 Apr 2018 01:31:00 +0200 Subject: [PATCH] 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 + } +}