diff --git a/framework/core/src/Admin/AdminServiceProvider.php b/framework/core/src/Admin/AdminServiceProvider.php index 4f815fcef..496e819c7 100644 --- a/framework/core/src/Admin/AdminServiceProvider.php +++ b/framework/core/src/Admin/AdminServiceProvider.php @@ -28,27 +28,12 @@ use Flarum\Http\RouteHandlerFactory; use Flarum\Locale\LocaleManager; use Flarum\Settings\Event\Saved; use Illuminate\Contracts\Container\Container; +use Illuminate\Contracts\Events\Dispatcher; class AdminServiceProvider extends AbstractServiceProvider { public function register(): void { - $this->booted(function (Container $container) { - /** @var Router $router */ - $router = $container->make(Router::class); - /** @var Config $config */ - $config = $container->make(Config::class); - - $router->middlewareGroup('admin', $container->make('flarum.admin.middleware')); - - $factory = $container->make(RouteHandlerFactory::class); - - $router->middleware('admin') - ->prefix($config->path('admin')) - ->name('admin.') - ->group(fn (Router $router) => (include __DIR__.'/routes.php')($router, $factory)); - }); - $this->container->singleton('flarum.admin.middleware', function () { return [ HttpMiddleware\InjectActorReference::class, @@ -101,18 +86,17 @@ class AdminServiceProvider extends AbstractServiceProvider }); } - public function boot(): void + public function boot(Container $container, Dispatcher $events): void { + $this->addRoutes($container); $this->loadViewsFrom(__DIR__.'/../../views', 'flarum.admin'); - $events = $this->container->make('events'); - $events->listen( [Enabled::class, Disabled::class, ClearingCache::class], - function () { + function () use ($container) { $recompile = new RecompileFrontendAssets( - $this->container->make('flarum.assets.admin'), - $this->container->make(LocaleManager::class) + $container->make('flarum.assets.admin'), + $container->make(LocaleManager::class) ); $recompile->flush(); } @@ -120,13 +104,30 @@ class AdminServiceProvider extends AbstractServiceProvider $events->listen( Saved::class, - function (Saved $event) { + function (Saved $event) use ($container) { $recompile = new RecompileFrontendAssets( - $this->container->make('flarum.assets.admin'), - $this->container->make(LocaleManager::class) + $container->make('flarum.assets.admin'), + $container->make(LocaleManager::class) ); $recompile->whenSettingsSaved($event); } ); } + + protected function addRoutes(Container $container) + { + /** @var Router $router */ + $router = $container->make(Router::class); + /** @var Config $config */ + $config = $container->make(Config::class); + + $router->middlewareGroup('admin', $container->make('flarum.admin.middleware')); + + $factory = $container->make(RouteHandlerFactory::class); + + $router->middleware('admin') + ->prefix($config->path('admin')) + ->name('admin.') + ->group(fn (Router $router) => (include __DIR__.'/routes.php')($router, $factory)); + } } diff --git a/framework/core/src/Api/ApiServiceProvider.php b/framework/core/src/Api/ApiServiceProvider.php index 49b64f7c7..89d2ea597 100644 --- a/framework/core/src/Api/ApiServiceProvider.php +++ b/framework/core/src/Api/ApiServiceProvider.php @@ -19,31 +19,19 @@ use Flarum\Foundation\ErrorHandling\JsonApiFormatter; use Flarum\Foundation\ErrorHandling\Registry; use Flarum\Foundation\ErrorHandling\Reporter; use Flarum\Http\Middleware as HttpMiddleware; +use Flarum\Http\RouteHandlerFactory; use Flarum\Http\Router; use Illuminate\Contracts\Container\Container; +use Illuminate\Http\Request; class ApiServiceProvider extends AbstractServiceProvider { public function register(): void { - $this->booted(function (Container $container) { - /** @var Router $router */ - $router = $container->make(Router::class); - /** @var Config $config */ - $config = $container->make(Config::class); - - $router->middlewareGroup('api', $container->make('flarum.api.middleware')); - - $router->middleware('api') - ->prefix($config->path('api')) - ->name('api.') - ->group(fn (Router $router) => (include __DIR__.'/routes.php')($router)); - }); - $this->container->singleton('flarum.api.throttlers', function () { return [ - 'bypassThrottlingAttribute' => function ($request) { - if ($request->getAttribute('bypassThrottling')) { + 'bypassThrottlingAttribute' => function (Request $request) { + if ($request->attributes->get('bypassThrottling')) { return false; } } @@ -108,6 +96,7 @@ class ApiServiceProvider extends AbstractServiceProvider public function boot(Container $container): void { + $this->addRoutes($container); $this->setNotificationSerializers(); AbstractSerializeController::setContainer($container); @@ -123,4 +112,21 @@ class ApiServiceProvider extends AbstractServiceProvider NotificationSerializer::setSubjectSerializer($type, $serializer); } } + + protected function addRoutes(Container $container) + { + /** @var Router $router */ + $router = $container->make(Router::class); + /** @var Config $config */ + $config = $container->make(Config::class); + + $router->middlewareGroup('api', $container->make('flarum.api.middleware')); + + $factory = $container->make(RouteHandlerFactory::class); + + $router->middleware('api') + ->prefix($config->path('api')) + ->name('api.') + ->group(fn (Router $router) => (include __DIR__.'/routes.php')($router, $factory)); + } } diff --git a/framework/core/src/Database/DatabaseServiceProvider.php b/framework/core/src/Database/DatabaseServiceProvider.php index dfae3d6ac..cdb918043 100644 --- a/framework/core/src/Database/DatabaseServiceProvider.php +++ b/framework/core/src/Database/DatabaseServiceProvider.php @@ -13,7 +13,6 @@ use Flarum\Foundation\AbstractServiceProvider; use Illuminate\Container\Container as ContainerImplementation; use Illuminate\Contracts\Container\Container; use Illuminate\Database\Capsule\Manager; -use Illuminate\Database\ConnectionInterface; use Illuminate\Database\ConnectionResolverInterface; class DatabaseServiceProvider extends AbstractServiceProvider @@ -32,7 +31,8 @@ class DatabaseServiceProvider extends AbstractServiceProvider return $manager; }); - $this->container->singleton(ConnectionResolverInterface::class, function (Container $container) { + $this->container->singleton('db', function (Container $container) { + /** @var Manager $manager */ $manager = $container->make(Manager::class); $manager->setAsGlobal(); $manager->bootEloquent(); @@ -43,19 +43,15 @@ class DatabaseServiceProvider extends AbstractServiceProvider return $dbManager; }); - $this->container->alias(ConnectionResolverInterface::class, 'db'); - - $this->container->singleton(ConnectionInterface::class, function (Container $container) { - $resolver = $container->make(ConnectionResolverInterface::class); + $this->container->singleton('db.connection', function (Container $container) { + /** @var ConnectionResolverInterface $resolver */ + $resolver = $container->make('db'); return $resolver->connection(); }); - $this->container->alias(ConnectionInterface::class, 'db.connection'); - $this->container->alias(ConnectionInterface::class, 'flarum.db'); - $this->container->singleton(MigrationRepositoryInterface::class, function (Container $container) { - return new DatabaseMigrationRepository($container['flarum.db'], 'migrations'); + return new DatabaseMigrationRepository($container['db.connection'], 'migrations'); }); $this->container->singleton('flarum.database.model_private_checkers', function () { diff --git a/framework/core/src/Extend/Concerns/ExtendsRoutes.php b/framework/core/src/Extend/Concerns/ExtendsRoutes.php index 7f56de06f..b1c4f90da 100644 --- a/framework/core/src/Extend/Concerns/ExtendsRoutes.php +++ b/framework/core/src/Extend/Concerns/ExtendsRoutes.php @@ -26,6 +26,7 @@ trait ExtendsRoutes /** @var Router $router */ $router = $container->make(Router::class); /** @var Config $config */ + $config = $container->make(Config::class); foreach ($this->removedRoutes as $routeName) { $router->forgetRoute($routeName); diff --git a/framework/core/src/Extend/ThrottleApi.php b/framework/core/src/Extend/ThrottleApi.php index afb3a05a0..37d381e3e 100644 --- a/framework/core/src/Extend/ThrottleApi.php +++ b/framework/core/src/Extend/ThrottleApi.php @@ -28,7 +28,7 @@ class ThrottleApi implements ExtenderInterface * The callable can be a closure or invokable class, and should accept: * - $request: The current `\Illuminate\Http\Request` request object. * `\Flarum\Http\RequestUtil::getActor($request)` can be used to get the current user. - * `$request->getAttribute('routeName')` can be used to get the current route. + * `$request->attributes->get('routeName')` can be used to get the current route. * Please note that every throttler runs by default on every route. * If you only want to throttle certain routes, you'll need to check for that inside your logic. * diff --git a/framework/core/src/Forum/Content/Discussion.php b/framework/core/src/Forum/Content/Discussion.php index c7b3f5bc7..f78b03440 100644 --- a/framework/core/src/Forum/Content/Discussion.php +++ b/framework/core/src/Forum/Content/Discussion.php @@ -104,6 +104,6 @@ class Discussion throw new RouteNotFoundException; } - return json_decode($response->getBody()); + return $response->getData(); } } diff --git a/framework/core/src/Forum/Content/Index.php b/framework/core/src/Forum/Content/Index.php index b8fa268d0..e2a2bbf96 100644 --- a/framework/core/src/Forum/Content/Index.php +++ b/framework/core/src/Forum/Content/Index.php @@ -54,7 +54,7 @@ class Index $document->content = $this->view->make('flarum.forum::frontend.content.index', compact('apiDocument', 'page')); $document->payload['apiDocument'] = $apiDocument; - $document->canonicalUrl = $this->url->base('forum').($defaultRoute === '/all' ? '' : $request->getUri()->getPath()); + $document->canonicalUrl = $this->url->base('forum').($defaultRoute === '/all' ? '' : $request->getUri()); $document->page = $page; $document->hasNextPage = isset($apiDocument->links->next); @@ -66,6 +66,6 @@ class Index */ protected function getApiDocument(Request $request, array $params): object { - return json_decode($this->api->withParentRequest($request)->withQueryParams($params)->get('/discussions')->getBody()); + return $this->api->withParentRequest($request)->withQueryParams($params)->get('/discussions')->getData(); } } diff --git a/framework/core/src/Forum/Controller/LogInController.php b/framework/core/src/Forum/Controller/LogInController.php index 4a7e1a402..5ecb6858e 100644 --- a/framework/core/src/Forum/Controller/LogInController.php +++ b/framework/core/src/Forum/Controller/LogInController.php @@ -45,7 +45,7 @@ class LogInController extends AbstractController $response = $this->apiClient->withParentRequest($request)->withBody($params)->post('/token'); if ($response->getStatusCode() === 200) { - $data = json_decode($response->getBody()); + $data = $response->getData(); $token = AccessToken::findValid($data->token); diff --git a/framework/core/src/Forum/Controller/RegisterController.php b/framework/core/src/Forum/Controller/RegisterController.php index 29a4909c9..4da8ead07 100644 --- a/framework/core/src/Forum/Controller/RegisterController.php +++ b/framework/core/src/Forum/Controller/RegisterController.php @@ -32,7 +32,7 @@ class RegisterController extends AbstractController $response = $this->api->withParentRequest($request)->withBody($params)->post('/users'); - $body = json_decode($response->getBody()); + $body = $response->getData(); if (isset($body->data)) { $userId = $body->data->id; diff --git a/framework/core/src/Forum/ForumServiceProvider.php b/framework/core/src/Forum/ForumServiceProvider.php index 96199322d..3785dcac0 100644 --- a/framework/core/src/Forum/ForumServiceProvider.php +++ b/framework/core/src/Forum/ForumServiceProvider.php @@ -34,33 +34,13 @@ use Flarum\Settings\SettingsRepositoryInterface; use Illuminate\Contracts\Container\Container; use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Contracts\View\Factory; +use Illuminate\Support\Arr; use Symfony\Contracts\Translation\TranslatorInterface; class ForumServiceProvider extends AbstractServiceProvider { public function register(): void { - $this->booted(function (Container $container) { - /** @var Router $router */ - $router = $container->make(Router::class); - /** @var Config $config */ - $config = $container->make(Config::class); - - $router->middlewareGroup('forum', $container->make('flarum.forum.middleware')); - - $factory = $container->make(RouteHandlerFactory::class); - - $router->middleware('forum') - ->prefix($config->path('forum')) - ->name('forum.') - ->group(fn (Router $router) => (include __DIR__.'/routes.php')($router, $factory)); - - $this->setDefaultRoute( - $router, - $container->make(SettingsRepositoryInterface::class) - ); - }); - $this->container->singleton('flarum.forum.middleware', function () { return [ HttpMiddleware\InjectActorReference::class, @@ -130,6 +110,7 @@ class ForumServiceProvider extends AbstractServiceProvider public function boot(Container $container, Dispatcher $events, Factory $view): void { + $this->addRoutes($container); $this->loadViewsFrom(__DIR__.'/../../views', 'flarum.forum'); $view->share([ @@ -181,10 +162,33 @@ class ForumServiceProvider extends AbstractServiceProvider ); } + protected function addRoutes(Container $container): void + { + /** @var Router $router */ + $router = $container->make(Router::class); + /** @var Config $config */ + $config = $container->make(Config::class); + + $router->middlewareGroup('forum', $container->make('flarum.forum.middleware')); + + $factory = $container->make(RouteHandlerFactory::class); + + $router->middleware('forum') + ->prefix($config->path('forum')) + ->name('forum.') + ->group(fn (Router $router) => (include __DIR__.'/routes.php')($router, $factory)); + + $this->setDefaultRoute( + $router, + $container->make(SettingsRepositoryInterface::class) + ); + } + protected function setDefaultRoute(Router $router, SettingsRepositoryInterface $settings): void { - $defaultRoute = $settings->get('default_route'); - $action = $router->getRoutes()->getByName($defaultRoute)?->getAction() ?? 'index'; - $router->get('/', $action)->name('default'); + $defaultRoutePath = ltrim($settings->get('default_route', '/all'), '/'); + /** @var \Illuminate\Routing\Route $route */ + $route = $router->getRoutes()->getRoutesByMethod()['GET'][$defaultRoutePath]; + $router->get('/', Arr::except($route->getAction(), ['as']))->name('forum.default'); } } diff --git a/framework/core/src/Foundation/Application.php b/framework/core/src/Foundation/Application.php index 42b418bf7..682aa42b5 100644 --- a/framework/core/src/Foundation/Application.php +++ b/framework/core/src/Foundation/Application.php @@ -9,8 +9,10 @@ namespace Flarum\Foundation; +use Flarum\Database\DatabaseServiceProvider; use Flarum\Foundation\Concerns\InteractsWithLaravel; use Flarum\Http\RoutingServiceProvider; +use Flarum\Settings\SettingsServiceProvider; use Illuminate\Container\Container as IlluminateContainer; use Illuminate\Contracts\Container\Container; use Illuminate\Contracts\Foundation\Application as LaravelApplication; @@ -39,6 +41,8 @@ class Application extends IlluminateContainer implements LaravelApplication protected array $loadedProviders = []; + protected bool $hasBeenBootstrapped = false; + public function __construct( protected Paths $paths ) { @@ -80,6 +84,13 @@ class Application extends IlluminateContainer implements LaravelApplication { $this->register(new EventServiceProvider($this)); $this->register(new RoutingServiceProvider($this)); + + // Because we need to check very early if the version of the app + // in the settings table matches the current version, we need + // to register the settings provider and therefore the database + // provider very early on. + $this->register(new DatabaseServiceProvider($this)); + $this->register(new SettingsServiceProvider($this)); } public function register($provider, $force = false): ServiceProvider @@ -194,16 +205,35 @@ class Application extends IlluminateContainer implements LaravelApplication } } + public function bootstrapWith(array $bootstrappers): void + { + $this->hasBeenBootstrapped = true; + + foreach ($bootstrappers as $bootstrapper) { + $this['events']->dispatch('bootstrapping: '.$bootstrapper, [$this]); + + $this->make($bootstrapper)->bootstrap($this); + + $this['events']->dispatch('bootstrapped: '.$bootstrapper, [$this]); + } + } + + public function hasBeenBootstrapped(): bool + { + return $this->hasBeenBootstrapped; + } + public function registerCoreContainerAliases(): void { $aliases = [ 'app' => [\Illuminate\Contracts\Container\Container::class, \Illuminate\Contracts\Foundation\Application::class, \Psr\Container\ContainerInterface::class], 'blade.compiler' => [\Illuminate\View\Compilers\BladeCompiler::class], 'cache' => [\Illuminate\Cache\CacheManager::class, \Illuminate\Contracts\Cache\Factory::class], + 'cache.filestore' => [\Illuminate\Cache\FileStore::class, \Illuminate\Contracts\Cache\Store::class], 'cache.store' => [\Illuminate\Cache\Repository::class, \Illuminate\Contracts\Cache\Repository::class], 'config' => [\Illuminate\Config\Repository::class, \Illuminate\Contracts\Config\Repository::class], 'container' => [\Illuminate\Contracts\Container\Container::class, \Psr\Container\ContainerInterface::class], - 'db' => [\Illuminate\Database\DatabaseManager::class], + 'db' => [\Illuminate\Database\ConnectionResolverInterface::class, \Illuminate\Database\DatabaseManager::class], 'db.connection' => [\Illuminate\Database\Connection::class, \Illuminate\Database\ConnectionInterface::class], 'events' => [\Illuminate\Events\Dispatcher::class, \Illuminate\Contracts\Events\Dispatcher::class], 'files' => [\Illuminate\Filesystem\Filesystem::class], @@ -211,9 +241,12 @@ class Application extends IlluminateContainer implements LaravelApplication 'filesystem.disk' => [\Illuminate\Contracts\Filesystem\Filesystem::class], 'filesystem.cloud' => [\Illuminate\Contracts\Filesystem\Cloud::class], 'flarum' => [self::class, \Illuminate\Contracts\Container\Container::class, \Illuminate\Contracts\Foundation\Application::class, \Psr\Container\ContainerInterface::class], + 'flarum.config' => [Config::class], 'flarum.paths' => [Paths::class], + 'flarum.settings' => [\Flarum\Settings\SettingsRepositoryInterface::class], 'hash' => [\Illuminate\Contracts\Hashing\Hasher::class], 'mailer' => [\Illuminate\Mail\Mailer::class, \Illuminate\Contracts\Mail\Mailer::class, \Illuminate\Contracts\Mail\MailQueue::class], + 'request' => [\Illuminate\Http\Request::class, \Symfony\Component\HttpFoundation\Request::class], 'router' => [\Flarum\Http\Router::class, \Illuminate\Routing\Router::class, \Illuminate\Contracts\Routing\Registrar::class, \Illuminate\Contracts\Routing\BindingRegistrar::class], 'session' => [\Illuminate\Session\SessionManager::class], 'session.store' => [\Illuminate\Session\Store::class, \Illuminate\Contracts\Session\Session::class], diff --git a/framework/core/src/Foundation/Bootstrap/BootProviders.php b/framework/core/src/Foundation/Bootstrap/BootProviders.php new file mode 100644 index 000000000..94a002057 --- /dev/null +++ b/framework/core/src/Foundation/Bootstrap/BootProviders.php @@ -0,0 +1,15 @@ +boot(); + } +} diff --git a/framework/core/src/Foundation/Bootstrap/IlluminateBootstrapperInterface.php b/framework/core/src/Foundation/Bootstrap/IlluminateBootstrapperInterface.php new file mode 100644 index 000000000..e571a7435 --- /dev/null +++ b/framework/core/src/Foundation/Bootstrap/IlluminateBootstrapperInterface.php @@ -0,0 +1,10 @@ +register(ErrorServiceProvider::class); + $app->register(LocaleServiceProvider::class); + $app->register(FilesystemServiceProvider::class); + $app->register(SessionServiceProvider::class); + $app->register(ValidationServiceProvider::class); + + $app->register(InstallServiceProvider::class); + + $this->registerLogger($app); + + $app->singleton( + SettingsRepositoryInterface::class, + UninstalledSettingsRepository::class + ); + + $app->singleton('view', function ($app) { + $engines = new EngineResolver(); + $engines->register('php', function () use ($app) { + return $app->make(PhpEngine::class); + }); + $finder = new FileViewFinder($app->make('files'), []); + $dispatcher = $app->make(Dispatcher::class); + + return new \Illuminate\View\Factory( + $engines, + $finder, + $dispatcher + ); + }); + } + + protected function registerLogger(Application $app): void + { + /** @var \Flarum\Foundation\Paths $paths */ + $paths = $app['flarum.paths']; + + $logPath = $paths->storage.'/logs/flarum-installer.log'; + $handler = new StreamHandler($logPath, Level::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/Foundation/Bootstrap/RegisterCache.php b/framework/core/src/Foundation/Bootstrap/RegisterCache.php new file mode 100644 index 000000000..18bc4963c --- /dev/null +++ b/framework/core/src/Foundation/Bootstrap/RegisterCache.php @@ -0,0 +1,25 @@ +singleton('cache.store', function ($app) { + return new CacheRepository($app->make('cache.filestore')); + }); + + $app->singleton('cache.filestore', function () use ($paths) { + return new FileStore(new Filesystem, $paths->storage.'/cache'); + }); + } +} diff --git a/framework/core/src/Foundation/Bootstrap/RegisterCoreProviders.php b/framework/core/src/Foundation/Bootstrap/RegisterCoreProviders.php new file mode 100644 index 000000000..68310eed2 --- /dev/null +++ b/framework/core/src/Foundation/Bootstrap/RegisterCoreProviders.php @@ -0,0 +1,66 @@ +register(AdminServiceProvider::class); + $app->register(ApiServiceProvider::class); + $app->register(BusServiceProvider::class); + $app->register(ConsoleServiceProvider::class); + $app->register(DiscussionServiceProvider::class); + $app->register(ExtensionServiceProvider::class); + $app->register(ErrorServiceProvider::class); + $app->register(FilesystemServiceProvider::class); + $app->register(FilterServiceProvider::class); + $app->register(FormatterServiceProvider::class); + $app->register(ForumServiceProvider::class); + $app->register(FrontendServiceProvider::class); + $app->register(GroupServiceProvider::class); + $app->register(HashServiceProvider::class); + $app->register(HttpServiceProvider::class); + $app->register(LocaleServiceProvider::class); + $app->register(MailServiceProvider::class); + $app->register(NotificationServiceProvider::class); + $app->register(PostServiceProvider::class); + $app->register(QueueServiceProvider::class); + $app->register(SearchServiceProvider::class); + $app->register(SessionServiceProvider::class); + $app->register(UpdateServiceProvider::class); + $app->register(UserServiceProvider::class); + $app->register(ValidationServiceProvider::class); + $app->register(ViewServiceProvider::class); + } +} diff --git a/framework/core/src/Foundation/Bootstrap/RegisterLogger.php b/framework/core/src/Foundation/Bootstrap/RegisterLogger.php new file mode 100644 index 000000000..2621202cc --- /dev/null +++ b/framework/core/src/Foundation/Bootstrap/RegisterLogger.php @@ -0,0 +1,31 @@ +storage.'/logs/flarum.log'; + $logLevel = $config->inDebugMode() ? Level::Debug : Level::Info; + $handler = new RotatingFileHandler($logPath, 0, $logLevel); + $handler->setFormatter(new LineFormatter(null, null, true, true)); + + $app->instance('log', new Logger('flarum', [$handler])); + $app->alias('log', LoggerInterface::class); + } +} diff --git a/framework/core/src/Foundation/Bootstrap/RegisterMaintenanceHandler.php b/framework/core/src/Foundation/Bootstrap/RegisterMaintenanceHandler.php new file mode 100644 index 000000000..00c4534b7 --- /dev/null +++ b/framework/core/src/Foundation/Bootstrap/RegisterMaintenanceHandler.php @@ -0,0 +1,14 @@ +instance('flarum.maintenance.handler', new MaintenanceModeHandler); + } +} diff --git a/framework/core/src/Foundation/Concerns/InteractsWithLaravel.php b/framework/core/src/Foundation/Concerns/InteractsWithLaravel.php index 30b833c4b..7f4e2b8b7 100644 --- a/framework/core/src/Foundation/Concerns/InteractsWithLaravel.php +++ b/framework/core/src/Foundation/Concerns/InteractsWithLaravel.php @@ -165,14 +165,6 @@ trait InteractsWithLaravel // } - /** - * @deprecated Not actually used/has no meaning in Flarum. - */ - public function bootstrapWith(array $bootstrappers) - { - // - } - public function getLocale() { return $this->make(LocaleManager::class)->getLocale(); @@ -190,14 +182,6 @@ trait InteractsWithLaravel return Arr::where($this->serviceProviders, fn ($value) => $value instanceof $name); } - /** - * @deprecated Not actually used/has no meaning in Flarum. - */ - public function hasBeenBootstrapped() - { - // - } - /** * @deprecated Not actually used/has no meaning in Flarum. */ diff --git a/framework/core/src/Foundation/InstalledApp.php b/framework/core/src/Foundation/InstalledApp.php index 6a12b3102..c4e432e8f 100644 --- a/framework/core/src/Foundation/InstalledApp.php +++ b/framework/core/src/Foundation/InstalledApp.php @@ -12,14 +12,11 @@ namespace Flarum\Foundation; use Flarum\Settings\SettingsRepositoryInterface; use Illuminate\Console\Command; use Illuminate\Contracts\Foundation\Application as ApplicationContract; -use Laminas\Stratigility\Middleware\OriginalMessages; -use Middlewares\BasePath; class InstalledApp implements AppInterface { public function __construct( protected ApplicationContract $app, - protected Config $config ) { } @@ -51,23 +48,17 @@ class InstalledApp implements AppInterface protected function getUpdaterMiddlewareStack(): array { return [ - new BasePath($this->basePath()), + // ]; } protected function getStandardMiddlewareStack(): array { return [ - new BasePath($this->basePath()), - new OriginalMessages, + // ]; } - protected function basePath(): string - { - return $this->config->url()->getPath() ?: '/'; - } - public function getConsoleCommands(): array { return array_map(function ($command) { diff --git a/framework/core/src/Foundation/InstalledSite.php b/framework/core/src/Foundation/InstalledSite.php index 9e205e06b..651266409 100644 --- a/framework/core/src/Foundation/InstalledSite.php +++ b/framework/core/src/Foundation/InstalledSite.php @@ -9,47 +9,9 @@ namespace Flarum\Foundation; -use Flarum\Admin\AdminServiceProvider; -use Flarum\Api\ApiServiceProvider; -use Flarum\Bus\BusServiceProvider; -use Flarum\Console\ConsoleServiceProvider; -use Flarum\Database\DatabaseServiceProvider; -use Flarum\Discussion\DiscussionServiceProvider; use Flarum\Extend\ExtenderInterface; -use Flarum\Extension\ExtensionServiceProvider; -use Flarum\Filesystem\FilesystemServiceProvider; -use Flarum\Filter\FilterServiceProvider; -use Flarum\Formatter\FormatterServiceProvider; -use Flarum\Forum\ForumServiceProvider; -use Flarum\Frontend\FrontendServiceProvider; -use Flarum\Group\GroupServiceProvider; -use Flarum\Http\HttpServiceProvider; -use Flarum\Locale\LocaleServiceProvider; -use Flarum\Mail\MailServiceProvider; -use Flarum\Notification\NotificationServiceProvider; -use Flarum\Post\PostServiceProvider; -use Flarum\Queue\QueueServiceProvider; -use Flarum\Search\SearchServiceProvider; -use Flarum\Settings\SettingsServiceProvider; -use Flarum\Update\UpdateServiceProvider; -use Flarum\User\SessionServiceProvider; -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\Contracts\Container\Container; use Illuminate\Contracts\Foundation\Application as ApplicationContract; -use Illuminate\Filesystem\Filesystem; -use Illuminate\Hashing\HashServiceProvider; -use Illuminate\Validation\ValidationServiceProvider; -use Illuminate\View\ViewServiceProvider; -use Monolog\Formatter\LineFormatter; -use Monolog\Handler\RotatingFileHandler; -use Monolog\Level; -use Monolog\Logger; -use Psr\Log\LoggerInterface; class InstalledSite implements SiteInterface { @@ -69,11 +31,10 @@ class InstalledSite implements SiteInterface * * @return InstalledApp */ - public function bootApp(): AppInterface + public function init(): AppInterface { return new InstalledApp( - $this->bootLaravel(), - $this->config + $this->createApp() ); } @@ -88,48 +49,14 @@ class InstalledSite implements SiteInterface return $this; } - protected function bootLaravel(): ApplicationContract + protected function createApp(): ApplicationContract { $app = new Application($this->paths); $app->instance('env', $this->config->environment()); $app->instance('flarum.config', $this->config); - $app->alias('flarum.config', Config::class); $app->instance('flarum.debug', $this->config->inDebugMode()); $app->instance('config', $this->getIlluminateConfig()); - $app->instance('flarum.maintenance.handler', new MaintenanceModeHandler); - - $this->registerLogger($app); - $this->registerCache($app); - - $app->register(AdminServiceProvider::class); - $app->register(ApiServiceProvider::class); - $app->register(BusServiceProvider::class); - $app->register(ConsoleServiceProvider::class); - $app->register(DatabaseServiceProvider::class); - $app->register(DiscussionServiceProvider::class); - $app->register(ExtensionServiceProvider::class); - $app->register(ErrorServiceProvider::class); - $app->register(FilesystemServiceProvider::class); - $app->register(FilterServiceProvider::class); - $app->register(FormatterServiceProvider::class); - $app->register(ForumServiceProvider::class); - $app->register(FrontendServiceProvider::class); - $app->register(GroupServiceProvider::class); - $app->register(HashServiceProvider::class); - $app->register(HttpServiceProvider::class); - $app->register(LocaleServiceProvider::class); - $app->register(MailServiceProvider::class); - $app->register(NotificationServiceProvider::class); - $app->register(PostServiceProvider::class); - $app->register(QueueServiceProvider::class); - $app->register(SearchServiceProvider::class); - $app->register(SessionServiceProvider::class); - $app->register(SettingsServiceProvider::class); - $app->register(UpdateServiceProvider::class); - $app->register(UserServiceProvider::class); - $app->register(ValidationServiceProvider::class); - $app->register(ViewServiceProvider::class); $app->booting(function () use ($app) { // Run all local-site extenders before booting service providers @@ -140,11 +67,20 @@ class InstalledSite implements SiteInterface } }); - $app->boot(); - return $app; } + public function bootstrappers(): array + { + return [ + \Flarum\Foundation\Bootstrap\RegisterMaintenanceHandler::class, + \Flarum\Foundation\Bootstrap\RegisterLogger::class, + \Flarum\Foundation\Bootstrap\RegisterCache::class, + \Flarum\Foundation\Bootstrap\RegisterCoreProviders::class, + \Flarum\Foundation\Bootstrap\BootProviders::class, + ]; + } + protected function getIlluminateConfig(): ConfigRepository { return new ConfigRepository([ @@ -162,28 +98,4 @@ class InstalledSite implements SiteInterface ] ]); } - - protected function registerLogger(Container $container): void - { - $logPath = $this->paths->storage.'/logs/flarum.log'; - $logLevel = $this->config->inDebugMode() ? Level::Debug : Level::Info; - $handler = new RotatingFileHandler($logPath, 0, $logLevel); - $handler->setFormatter(new LineFormatter(null, null, true, true)); - - $container->instance('log', new Logger('flarum', [$handler])); - $container->alias('log', LoggerInterface::class); - } - - protected function registerCache(Container $container): void - { - $container->singleton('cache.store', function ($container) { - return new CacheRepository($container->make('cache.filestore')); - }); - $container->alias('cache.store', Repository::class); - - $container->singleton('cache.filestore', function () { - return new FileStore(new Filesystem, $this->paths->storage.'/cache'); - }); - $container->alias('cache.filestore', Store::class); - } } diff --git a/framework/core/src/Foundation/SafeBooter.php b/framework/core/src/Foundation/SafeBooter.php new file mode 100644 index 000000000..15ac625a7 --- /dev/null +++ b/framework/core/src/Foundation/SafeBooter.php @@ -0,0 +1,97 @@ +app->boot(); + } catch (Throwable $e) { + // Apply response code first so whatever happens, it's set before anything is printed + http_response_code(500); + + try { + $this->cleanBootExceptionLog($e); + } catch (Throwable $e) { + // Ignore errors in logger. The important goal is to log the original error + } + + $this->fallbackBootExceptionLog($e); + } + } + + /** + * Attempt to log the boot exception in a clean way and stop the script execution. + * This means looking for debug mode and/or our normal error logger. + * There is always a risk for this to fail, + * for example if the container bindings aren't present + * or if there is a filesystem error. + * @param Throwable $error + * @throws Throwable + */ + private function cleanBootExceptionLog(Throwable $error): void + { + if ($this->app->has('flarum.config') && resolve('flarum.config')->inDebugMode()) { + // If the application booted far enough for the config to be available, we will check for debug mode + // Since the config is loaded very early, it is very likely to be available from the container + $message = $error->getMessage(); + $file = $error->getFile(); + $line = $error->getLine(); + $type = get_class($error); + + echo << + $message
+ thrown in $file on line $line + +
$error
+ERROR; + exit(1); + } elseif ($this->app->has(LoggerInterface::class)) { + // If the application booted far enough for the logger to be available, we will log the error there + // Considering most boot errors are related to database or extensions, the logger should already be loaded + // We check for LoggerInterface binding because it's a constructor dependency of LogReporter, + // then instantiate LogReporter through the container for automatic dependency injection + resolve(LogReporter::class)->report($error); + + echo 'Flarum encountered a boot error. Details have been logged to the Flarum log file.'; + exit(1); + } + } + + /** + * If the clean logging doesn't work, then we have a last opportunity. + * Here we need to be extra careful not to include anything that might be sensitive on the page. + * + * @throws Throwable + */ + private function fallbackBootExceptionLog(Throwable $error): void + { + echo 'Flarum encountered a boot error. Details have been logged to the system PHP log file.
'; + + // Throwing the exception ensures it will be visible with PHP display_errors=On + // but invisible if that feature is turned off + // PHP will also automatically choose a valid place to log it based on the system settings + throw $error; + } +} diff --git a/framework/core/src/Foundation/SiteInterface.php b/framework/core/src/Foundation/SiteInterface.php index c48b17521..336c957a2 100644 --- a/framework/core/src/Foundation/SiteInterface.php +++ b/framework/core/src/Foundation/SiteInterface.php @@ -14,5 +14,10 @@ interface SiteInterface /** * Create and boot a Flarum application instance. */ - public function bootApp(): AppInterface; + public function init(): AppInterface; + + /** + * Bootstrappers make up the booting process of the Application. + */ + public function bootstrappers(): array; } diff --git a/framework/core/src/Foundation/UninstalledSite.php b/framework/core/src/Foundation/UninstalledSite.php index 3fea1ad7f..7e57a1968 100644 --- a/framework/core/src/Foundation/UninstalledSite.php +++ b/framework/core/src/Foundation/UninstalledSite.php @@ -10,23 +10,8 @@ namespace Flarum\Foundation; use Flarum\Install\Installer; -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\Container\Container; -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; +use Illuminate\Contracts\Foundation\Application as ApplicationContract; class UninstalledSite implements SiteInterface { @@ -36,17 +21,14 @@ class UninstalledSite implements SiteInterface ) { } - /** - * Create and boot a Flarum application instance. - */ - public function bootApp(): AppInterface + public function init(): AppInterface { return new Installer( - $this->bootLaravel() + $this->createApp() ); } - protected function bootLaravel(): Container + protected function createApp(): ApplicationContract { $app = new Application($this->paths); @@ -54,43 +36,19 @@ class UninstalledSite implements SiteInterface $app->instance('flarum.config', new Config(['url' => $this->baseUrl])); $app->alias('flarum.config', Config::class); $app->instance('flarum.debug', true); - $app->instance('config', $config = $this->getIlluminateConfig()); - - $this->registerLogger($app); - - $app->register(ErrorServiceProvider::class); - $app->register(LocaleServiceProvider::class); - $app->register(FilesystemServiceProvider::class); - $app->register(SessionServiceProvider::class); - $app->register(ValidationServiceProvider::class); - - $app->register(InstallServiceProvider::class); - - $app->singleton( - SettingsRepositoryInterface::class, - UninstalledSettingsRepository::class - ); - - $app->singleton('view', function ($app) { - $engines = new EngineResolver(); - $engines->register('php', function () use ($app) { - return $app->make(PhpEngine::class); - }); - $finder = new FileViewFinder($app->make('files'), []); - $dispatcher = $app->make(Dispatcher::class); - - return new \Illuminate\View\Factory( - $engines, - $finder, - $dispatcher - ); - }); - - $app->boot(); + $app->instance('config', $this->getIlluminateConfig()); return $app; } + public function bootstrappers(): array + { + return [ + \Flarum\Foundation\Bootstrap\PrepareInstaller::class, + \Flarum\Foundation\Bootstrap\BootProviders::class, + ]; + } + protected function getIlluminateConfig(): ConfigRepository { return new ConfigRepository([ @@ -104,14 +62,4 @@ class UninstalledSite implements SiteInterface ], ]); } - - protected function registerLogger(Container $container): void - { - $logPath = $this->paths->storage.'/logs/flarum-installer.log'; - $handler = new StreamHandler($logPath, Logger::DEBUG); - $handler->setFormatter(new LineFormatter(null, null, true, true)); - - $container->instance('log', new Logger('Flarum Installer', [$handler])); - $container->alias('log', LoggerInterface::class); - } } diff --git a/framework/core/src/Http/Middleware/StartSession.php b/framework/core/src/Http/Middleware/StartSession.php index 0816c2df0..89aeb1e2c 100644 --- a/framework/core/src/Http/Middleware/StartSession.php +++ b/framework/core/src/Http/Middleware/StartSession.php @@ -15,7 +15,6 @@ use Illuminate\Contracts\Config\Repository as ConfigRepository; use Illuminate\Contracts\Session\Session; use Illuminate\Http\Request; use Illuminate\Session\Store; -use Illuminate\Support\Arr; use SessionHandlerInterface; use Symfony\Component\HttpFoundation\Response; @@ -53,7 +52,7 @@ class StartSession implements IlluminateMiddlewareInterface return new Store( $this->config['cookie'], $this->handler, - Arr::get($request->getCookieParams(), $this->cookie->getName($this->config['cookie'])) + $request->cookie($this->cookie->getName($this->config['cookie'])) ); } diff --git a/framework/core/src/Http/RouteHandlerFactory.php b/framework/core/src/Http/RouteHandlerFactory.php index b18f21fff..969a5eece 100644 --- a/framework/core/src/Http/RouteHandlerFactory.php +++ b/framework/core/src/Http/RouteHandlerFactory.php @@ -12,6 +12,7 @@ namespace Flarum\Http; use Closure; use Flarum\Frontend\Controller as FrontendController; use Illuminate\Contracts\Container\Container; +use Illuminate\Http\Request; use Psr\Http\Server\RequestHandlerInterface; /** @@ -38,13 +39,18 @@ class RouteHandlerFactory public function toFrontend(string $frontend, callable|string|null $content = null): callable { - $frontend = $this->container->make("flarum.frontend.$frontend"); + return function (Request $request) use ($frontend, $content): mixed { + $frontend = $this->container->make("flarum.frontend.$frontend"); - if ($content) { - $frontend->content(is_callable($content) ? $content : $this->container->make($content)); - } + if ($content) { + $frontend->content(is_callable($content) ? $content : $this->container->make($content)); + } - return new FrontendController($frontend); + return $this->container->call( + $this->container->make(FrontendController::class, compact('frontend')), + compact('request') + ); + }; } public function toForum(string $content = null): Closure diff --git a/framework/core/src/Http/RoutingServiceProvider.php b/framework/core/src/Http/RoutingServiceProvider.php index 9da529fa8..f3caebe42 100644 --- a/framework/core/src/Http/RoutingServiceProvider.php +++ b/framework/core/src/Http/RoutingServiceProvider.php @@ -2,6 +2,7 @@ namespace Flarum\Http; +use Flarum\Foundation\Config; use Illuminate\Contracts\Container\Container; use Illuminate\Routing\RoutingServiceProvider as IlluminateRoutingServiceProvider; @@ -12,6 +13,11 @@ class RoutingServiceProvider extends IlluminateRoutingServiceProvider $this->app->singleton('router', function (Container $container) { return new Router($container['events'], $container); }); + + $this->app->booted(function (Container $container) { + $container['router']->getRoutes()->refreshNameLookups(); + $container['router']->getRoutes()->refreshActionLookups(); + }); } protected function registerUrlGenerator(): void @@ -24,11 +30,17 @@ class RoutingServiceProvider extends IlluminateRoutingServiceProvider // and all the registered routes will be available to the generator. $container->instance('routes', $routes); - return new UrlGenerator( + $url = new UrlGenerator( $routes, $container->rebinding( 'request', $this->requestRebinder() ), $container['config']['app.asset_url'] ); + + $url->setConfig( + $container->make(Config::class) + ); + + return $url; }); } } diff --git a/framework/core/src/Http/Server.php b/framework/core/src/Http/Server.php index a7091ec30..9dba4ef88 100644 --- a/framework/core/src/Http/Server.php +++ b/framework/core/src/Http/Server.php @@ -9,16 +9,11 @@ namespace Flarum\Http; -use Flarum\Foundation\AppInterface; -use Flarum\Foundation\ErrorHandling\LogReporter; use Flarum\Foundation\SiteInterface; -use Illuminate\Contracts\Container\Container; use Illuminate\Contracts\Foundation\Application; use Illuminate\Http\Request; use Illuminate\Routing\Pipeline; -use Psr\Log\LoggerInterface; use Symfony\Component\HttpFoundation\Response; -use Throwable; class Server { @@ -29,15 +24,21 @@ class Server public function listen(): void { - $siteApp = $this->safelyBoot(); + $siteApp = $this->site->init(); $app = $siteApp->getContainer(); $globalMiddleware = $siteApp->getMiddlewareStack(); - $this->send(Request::capture(), $app, $globalMiddleware); + $this + ->handle(Request::capture(), $app, $globalMiddleware) + ->send(); } - public function send(Request $request, Application $app, array $globalMiddleware): Response + public function handle(Request $request, Application $app, array $globalMiddleware): Response { + $app->instance('request', $request); + + $this->bootstrap($app); + return (new Pipeline($app)) ->send($request) ->through($globalMiddleware) @@ -46,86 +47,12 @@ class Server }); } - /** - * Try to boot Flarum, and retrieve the app's HTTP request handler. - * - * We catch all exceptions happening during this process and format them to - * prevent exposure of sensitive information. - * - * @throws Throwable - */ - private function safelyBoot(): AppInterface + public function bootstrap(Application $app): void { - try { - return $this->site->bootApp(); - } catch (Throwable $e) { - // Apply response code first so whatever happens, it's set before anything is printed - http_response_code(500); - - try { - $this->cleanBootExceptionLog($e); - } catch (Throwable $e) { - // Ignore errors in logger. The important goal is to log the original error - } - - $this->fallbackBootExceptionLog($e); + if (! $app->hasBeenBootstrapped()) { + $app->bootstrapWith( + $this->site->bootstrappers() + ); } } - - /** - * Attempt to log the boot exception in a clean way and stop the script execution. - * This means looking for debug mode and/or our normal error logger. - * There is always a risk for this to fail, - * for example if the container bindings aren't present - * or if there is a filesystem error. - * @param Throwable $error - * @throws Throwable - */ - private function cleanBootExceptionLog(Throwable $error): void - { - $container = resolve(Container::class); - - if ($container->has('flarum.config') && resolve('flarum.config')->inDebugMode()) { - // If the application booted far enough for the config to be available, we will check for debug mode - // Since the config is loaded very early, it is very likely to be available from the container - $message = $error->getMessage(); - $file = $error->getFile(); - $line = $error->getLine(); - $type = get_class($error); - - echo << - $message
- thrown in $file on line $line - -
$error
-ERROR; - exit(1); - } elseif ($container->has(LoggerInterface::class)) { - // If the application booted far enough for the logger to be available, we will log the error there - // Considering most boot errors are related to database or extensions, the logger should already be loaded - // We check for LoggerInterface binding because it's a constructor dependency of LogReporter, - // then instantiate LogReporter through the container for automatic dependency injection - resolve(LogReporter::class)->report($error); - - echo 'Flarum encountered a boot error. Details have been logged to the Flarum log file.'; - exit(1); - } - } - - /** - * If the clean logging doesn't work, then we have a last opportunity. - * Here we need to be extra careful not to include anything that might be sensitive on the page. - * - * @throws Throwable - */ - private function fallbackBootExceptionLog(Throwable $error): void - { - echo 'Flarum encountered a boot error. Details have been logged to the system PHP log file.
'; - - // Throwing the exception ensures it will be visible with PHP display_errors=On - // but invisible if that feature is turned off - // PHP will also automatically choose a valid place to log it based on the system settings - throw $error; - } } diff --git a/framework/core/src/Settings/SettingsServiceProvider.php b/framework/core/src/Settings/SettingsServiceProvider.php index ff8fd02cb..23268ed98 100644 --- a/framework/core/src/Settings/SettingsServiceProvider.php +++ b/framework/core/src/Settings/SettingsServiceProvider.php @@ -27,7 +27,7 @@ class SettingsServiceProvider extends AbstractServiceProvider ]); }); - $this->container->singleton(SettingsRepositoryInterface::class, function (Container $container) { + $this->container->singleton('flarum.settings', function (Container $container) { return new DefaultSettingsRepository( new MemoryCacheSettingsRepository( new DatabaseSettingsRepository( @@ -37,8 +37,6 @@ class SettingsServiceProvider extends AbstractServiceProvider $container->make('flarum.settings.default') ); }); - - $this->container->alias(SettingsRepositoryInterface::class, 'flarum.settings'); } public function boot(Dispatcher $events, SettingsValidator $settingsValidator): void diff --git a/php-packages/phpstan/bootstrap.php b/php-packages/phpstan/bootstrap.php index dcc4d8a2d..f39c7a5af 100644 --- a/php-packages/phpstan/bootstrap.php +++ b/php-packages/phpstan/bootstrap.php @@ -19,4 +19,4 @@ if (! function_exists('database_path')) { } $site = (new \Flarum\Testing\integration\Setup\Bootstrapper())->run(); -$site->bootApp(); +$site->init(); diff --git a/php-packages/testing/src/integration/TestCase.php b/php-packages/testing/src/integration/TestCase.php index 12459ba61..d6476efb3 100644 --- a/php-packages/testing/src/integration/TestCase.php +++ b/php-packages/testing/src/integration/TestCase.php @@ -55,7 +55,7 @@ abstract class TestCase extends \PHPUnit\Framework\TestCase $this->extenders ); - $this->app = $bootstrapper->run()->bootApp(); + $this->app = $bootstrapper->run()->init(); $this->database = $bootstrapper->database; @@ -215,7 +215,7 @@ abstract class TestCase extends \PHPUnit\Framework\TestCase */ protected function send(Request $request): Response { - return $this->server()->send($request, $this->app->getContainer(), []); + return $this->server()->handle($request, $this->app->getContainer(), []); } /**