1
0
mirror of https://github.com/flarum/core.git synced 2025-08-28 18:40:46 +02:00

chore: replace request handling with illuminate http & router

This commit is contained in:
Sami Mazouz
2023-08-11 14:19:44 +01:00
parent a60e3d174f
commit 7d4549ea34
28 changed files with 503 additions and 892 deletions

View File

@@ -12,6 +12,7 @@ namespace Flarum\Admin;
use Flarum\Extension\Event\Disabled; use Flarum\Extension\Event\Disabled;
use Flarum\Extension\Event\Enabled; use Flarum\Extension\Event\Enabled;
use Flarum\Foundation\AbstractServiceProvider; use Flarum\Foundation\AbstractServiceProvider;
use Flarum\Foundation\Config;
use Flarum\Foundation\ErrorHandling\Registry; use Flarum\Foundation\ErrorHandling\Registry;
use Flarum\Foundation\ErrorHandling\Reporter; use Flarum\Foundation\ErrorHandling\Reporter;
use Flarum\Foundation\ErrorHandling\ViewFormatter; use Flarum\Foundation\ErrorHandling\ViewFormatter;
@@ -22,39 +23,39 @@ use Flarum\Frontend\AddTranslations;
use Flarum\Frontend\Compiler\Source\SourceCollector; use Flarum\Frontend\Compiler\Source\SourceCollector;
use Flarum\Frontend\RecompileFrontendAssets; use Flarum\Frontend\RecompileFrontendAssets;
use Flarum\Http\Middleware as HttpMiddleware; use Flarum\Http\Middleware as HttpMiddleware;
use Flarum\Http\RouteCollection; use Flarum\Http\Router;
use Flarum\Http\RouteHandlerFactory; use Flarum\Http\RouteHandlerFactory;
use Flarum\Http\UrlGenerator;
use Flarum\Locale\LocaleManager; use Flarum\Locale\LocaleManager;
use Flarum\Settings\Event\Saved; use Flarum\Settings\Event\Saved;
use Illuminate\Contracts\Container\Container; use Illuminate\Contracts\Container\Container;
use Laminas\Stratigility\MiddlewarePipe;
class AdminServiceProvider extends AbstractServiceProvider class AdminServiceProvider extends AbstractServiceProvider
{ {
public function register(): void public function register(): void
{ {
$this->container->extend(UrlGenerator::class, function (UrlGenerator $url, Container $container) { $this->booted(function (Container $container) {
return $url->addCollection('admin', $container->make('flarum.admin.routes'), 'admin'); /** @var Router $router */
}); $router = $container->make(Router::class);
/** @var Config $config */
$config = $container->make(Config::class);
$this->container->singleton('flarum.admin.routes', function () { $router->middlewareGroup('admin', $container->make('flarum.admin.middleware'));
$routes = new RouteCollection;
$this->populateRoutes($routes);
return $routes; $factory = $container->make(RouteHandlerFactory::class);
$router->middleware('admin')->prefix($config->path('admin'))->group(
fn (Router $router) => (include __DIR__.'/routes.php')($router, $factory)
);
}); });
$this->container->singleton('flarum.admin.middleware', function () { $this->container->singleton('flarum.admin.middleware', function () {
return [ return [
HttpMiddleware\InjectActorReference::class, HttpMiddleware\InjectActorReference::class,
'flarum.admin.error_handler', 'flarum.admin.error_handler',
HttpMiddleware\ParseJsonBody::class,
HttpMiddleware\StartSession::class, HttpMiddleware\StartSession::class,
HttpMiddleware\RememberFromCookie::class, HttpMiddleware\RememberFromCookie::class,
HttpMiddleware\AuthenticateWithSession::class, HttpMiddleware\AuthenticateWithSession::class,
HttpMiddleware\SetLocale::class, HttpMiddleware\SetLocale::class,
'flarum.admin.route_resolver',
HttpMiddleware\CheckCsrfToken::class, HttpMiddleware\CheckCsrfToken::class,
Middleware\RequireAdministrateAbility::class, Middleware\RequireAdministrateAbility::class,
HttpMiddleware\ReferrerPolicyHeader::class, HttpMiddleware\ReferrerPolicyHeader::class,
@@ -71,22 +72,6 @@ class AdminServiceProvider extends AbstractServiceProvider
); );
}); });
$this->container->bind('flarum.admin.route_resolver', function (Container $container) {
return new HttpMiddleware\ResolveRoute($container->make('flarum.admin.routes'));
});
$this->container->singleton('flarum.admin.handler', function (Container $container) {
$pipe = new MiddlewarePipe;
foreach ($container->make('flarum.admin.middleware') as $middleware) {
$pipe->pipe($container->make($middleware));
}
$pipe->pipe(new HttpMiddleware\ExecuteRoute());
return $pipe;
});
$this->container->bind('flarum.assets.admin', function (Container $container) { $this->container->bind('flarum.assets.admin', function (Container $container) {
/** @var \Flarum\Frontend\Assets $assets */ /** @var \Flarum\Frontend\Assets $assets */
$assets = $container->make('flarum.assets.factory')('admin'); $assets = $container->make('flarum.assets.factory')('admin');
@@ -143,12 +128,4 @@ class AdminServiceProvider extends AbstractServiceProvider
} }
); );
} }
protected function populateRoutes(RouteCollection $routes): void
{
$factory = $this->container->make(RouteHandlerFactory::class);
$callback = include __DIR__.'/routes.php';
$callback($routes, $factory);
}
} }

View File

@@ -9,17 +9,22 @@
namespace Flarum\Admin\Middleware; namespace Flarum\Admin\Middleware;
use Psr\Http\Message\ResponseInterface as Response; use Closure;
use Psr\Http\Message\ServerRequestInterface as Request; use Flarum\Http\Middleware\IlluminateMiddlewareInterface;
use Psr\Http\Server\MiddlewareInterface as Middleware; use Illuminate\Http\Request;
use Psr\Http\Server\RequestHandlerInterface as Handler; use Symfony\Component\HttpFoundation\Response;
class DisableBrowserCache implements Middleware class DisableBrowserCache implements IlluminateMiddlewareInterface
{ {
public function process(Request $request, Handler $handler): Response /**
* @inheritDoc
*/
public function handle(Request $request, Closure $next): Response
{ {
$response = $handler->handle($request); $response = $next($request);
return $response->withHeader('Cache-Control', 'max-age=0, no-store'); $response->headers->set('Cache-Control', 'max-age=0, no-store');
return $response;
} }
} }

View File

@@ -9,18 +9,18 @@
namespace Flarum\Admin\Middleware; namespace Flarum\Admin\Middleware;
use Closure;
use Flarum\Http\Middleware\IlluminateMiddlewareInterface;
use Flarum\Http\RequestUtil; use Flarum\Http\RequestUtil;
use Psr\Http\Message\ResponseInterface as Response; use Illuminate\Http\Request;
use Psr\Http\Message\ServerRequestInterface as Request; use Symfony\Component\HttpFoundation\Response;
use Psr\Http\Server\MiddlewareInterface as Middleware;
use Psr\Http\Server\RequestHandlerInterface as Handler;
class RequireAdministrateAbility implements Middleware class RequireAdministrateAbility implements IlluminateMiddlewareInterface
{ {
public function process(Request $request, Handler $handler): Response public function handle(Request $request, Closure $next): Response
{ {
RequestUtil::getActor($request)->assertAdmin(); RequestUtil::getActor($request)->assertAdmin();
return $handler->handle($request); return $next($request);
} }
} }

View File

@@ -9,19 +9,17 @@
use Flarum\Admin\Content\Index; use Flarum\Admin\Content\Index;
use Flarum\Admin\Controller\UpdateExtensionController; use Flarum\Admin\Controller\UpdateExtensionController;
use Flarum\Http\RouteCollection; use Flarum\Http\Router;
use Flarum\Http\RouteHandlerFactory; use Flarum\Http\RouteHandlerFactory;
return function (RouteCollection $map, RouteHandlerFactory $route) { return function (Router $router, RouteHandlerFactory $factory) {
$map->get(
'/', $router
'index', ->get('/', $factory->toAdmin(Index::class))
$route->toAdmin(Index::class) ->name('index');
);
$router
->post('/extensions/{name}', $factory->toController(UpdateExtensionController::class))
->name('extensions.update');
$map->post(
'/extensions/{name}',
'extensions.update',
$route->toController(UpdateExtensionController::class)
);
}; };

View File

@@ -14,29 +14,29 @@ use Flarum\Api\Serializer\AbstractSerializer;
use Flarum\Api\Serializer\BasicDiscussionSerializer; use Flarum\Api\Serializer\BasicDiscussionSerializer;
use Flarum\Api\Serializer\NotificationSerializer; use Flarum\Api\Serializer\NotificationSerializer;
use Flarum\Foundation\AbstractServiceProvider; use Flarum\Foundation\AbstractServiceProvider;
use Flarum\Foundation\Config;
use Flarum\Foundation\ErrorHandling\JsonApiFormatter; use Flarum\Foundation\ErrorHandling\JsonApiFormatter;
use Flarum\Foundation\ErrorHandling\Registry; use Flarum\Foundation\ErrorHandling\Registry;
use Flarum\Foundation\ErrorHandling\Reporter; use Flarum\Foundation\ErrorHandling\Reporter;
use Flarum\Http\Middleware as HttpMiddleware; use Flarum\Http\Middleware as HttpMiddleware;
use Flarum\Http\RouteCollection; use Flarum\Http\Router;
use Flarum\Http\RouteHandlerFactory;
use Flarum\Http\UrlGenerator;
use Illuminate\Contracts\Container\Container; use Illuminate\Contracts\Container\Container;
use Laminas\Stratigility\MiddlewarePipe;
class ApiServiceProvider extends AbstractServiceProvider class ApiServiceProvider extends AbstractServiceProvider
{ {
public function register(): void public function register(): void
{ {
$this->container->extend(UrlGenerator::class, function (UrlGenerator $url, Container $container) { $this->booted(function (Container $container) {
return $url->addCollection('api', $container->make('flarum.api.routes'), 'api'); /** @var Router $router */
}); $router = $container->make(Router::class);
/** @var Config $config */
$config = $container->make(Config::class);
$this->container->singleton('flarum.api.routes', function () { $router->middlewareGroup('api', $container->make('flarum.api.middleware'));
$routes = new RouteCollection;
$this->populateRoutes($routes);
return $routes; $router->middleware('api')->prefix($config->path('api'))->group(
fn (Router $router) => (include __DIR__.'/routes.php')($router)
);
}); });
$this->container->singleton('flarum.api.throttlers', function () { $this->container->singleton('flarum.api.throttlers', function () {
@@ -57,14 +57,12 @@ class ApiServiceProvider extends AbstractServiceProvider
return [ return [
HttpMiddleware\InjectActorReference::class, HttpMiddleware\InjectActorReference::class,
'flarum.api.error_handler', 'flarum.api.error_handler',
HttpMiddleware\ParseJsonBody::class,
Middleware\FakeHttpMethods::class, Middleware\FakeHttpMethods::class,
HttpMiddleware\StartSession::class, HttpMiddleware\StartSession::class,
HttpMiddleware\RememberFromCookie::class, HttpMiddleware\RememberFromCookie::class,
HttpMiddleware\AuthenticateWithSession::class, HttpMiddleware\AuthenticateWithSession::class,
HttpMiddleware\AuthenticateWithHeader::class, HttpMiddleware\AuthenticateWithHeader::class,
HttpMiddleware\SetLocale::class, HttpMiddleware\SetLocale::class,
'flarum.api.route_resolver',
HttpMiddleware\CheckCsrfToken::class, HttpMiddleware\CheckCsrfToken::class,
Middleware\ThrottleApi::class Middleware\ThrottleApi::class
]; ];
@@ -78,22 +76,6 @@ class ApiServiceProvider extends AbstractServiceProvider
); );
}); });
$this->container->bind('flarum.api.route_resolver', function (Container $container) {
return new HttpMiddleware\ResolveRoute($container->make('flarum.api.routes'));
});
$this->container->singleton('flarum.api.handler', function (Container $container) {
$pipe = new MiddlewarePipe;
foreach ($this->container->make('flarum.api.middleware') as $middleware) {
$pipe->pipe($container->make($middleware));
}
$pipe->pipe(new HttpMiddleware\ExecuteRoute());
return $pipe;
});
$this->container->singleton('flarum.api.notification_serializers', function () { $this->container->singleton('flarum.api.notification_serializers', function () {
return [ return [
'discussionRenamed' => BasicDiscussionSerializer::class 'discussionRenamed' => BasicDiscussionSerializer::class
@@ -103,7 +85,6 @@ class ApiServiceProvider extends AbstractServiceProvider
$this->container->singleton('flarum.api_client.exclude_middleware', function () { $this->container->singleton('flarum.api_client.exclude_middleware', function () {
return [ return [
HttpMiddleware\InjectActorReference::class, HttpMiddleware\InjectActorReference::class,
HttpMiddleware\ParseJsonBody::class,
Middleware\FakeHttpMethods::class, Middleware\FakeHttpMethods::class,
HttpMiddleware\StartSession::class, HttpMiddleware\StartSession::class,
HttpMiddleware\AuthenticateWithSession::class, HttpMiddleware\AuthenticateWithSession::class,
@@ -113,22 +94,14 @@ class ApiServiceProvider extends AbstractServiceProvider
]; ];
}); });
$this->container->singleton(Client::class, function ($container) { $this->container->singleton(Client::class, function (Container $container) {
$pipe = new MiddlewarePipe;
$exclude = $container->make('flarum.api_client.exclude_middleware'); $exclude = $container->make('flarum.api_client.exclude_middleware');
$middlewareStack = array_filter($container->make('flarum.api.middleware'), function ($middlewareClass) use ($exclude) { $middlewareStack = array_filter($container->make('flarum.api.middleware'), function ($middlewareClass) use ($exclude) {
return ! in_array($middlewareClass, $exclude); return ! in_array($middlewareClass, $exclude);
}); });
foreach ($middlewareStack as $middleware) { return new Client($middlewareStack, $container);
$pipe->pipe($container->make($middleware));
}
$pipe->pipe(new HttpMiddleware\ExecuteRoute());
return new Client($pipe);
}); });
} }
@@ -149,12 +122,4 @@ class ApiServiceProvider extends AbstractServiceProvider
NotificationSerializer::setSubjectSerializer($type, $serializer); NotificationSerializer::setSubjectSerializer($type, $serializer);
} }
} }
protected function populateRoutes(RouteCollection $routes): void
{
$factory = $this->container->make(RouteHandlerFactory::class);
$callback = include __DIR__.'/routes.php';
$callback($routes, $factory);
}
} }

View File

@@ -9,23 +9,23 @@
namespace Flarum\Api\Middleware; namespace Flarum\Api\Middleware;
use Psr\Http\Message\ResponseInterface as Response; use Closure;
use Psr\Http\Message\ServerRequestInterface as Request; use Flarum\Http\Middleware\IlluminateMiddlewareInterface;
use Psr\Http\Server\MiddlewareInterface as Middleware; use Illuminate\Http\Request;
use Psr\Http\Server\RequestHandlerInterface as Handler; use Symfony\Component\HttpFoundation\Response;
class FakeHttpMethods implements Middleware class FakeHttpMethods implements IlluminateMiddlewareInterface
{ {
const HEADER_NAME = 'x-http-method-override'; const HEADER_NAME = 'x-http-method-override';
public function process(Request $request, Handler $handler): Response public function handle(Request $request, Closure $next): Response
{ {
if ($request->getMethod() === 'POST' && $request->hasHeader(self::HEADER_NAME)) { if ($request->getMethod() === 'POST' && $request->hasHeader(self::HEADER_NAME)) {
$fakeMethod = $request->getHeaderLine(self::HEADER_NAME); $fakeMethod = $request->header(self::HEADER_NAME);
$request = $request->withMethod(strtoupper($fakeMethod)); $request->setMethod(strtoupper($fakeMethod));
} }
return $handler->handle($request); return $next($request);
} }
} }

View File

@@ -9,26 +9,26 @@
namespace Flarum\Api\Middleware; namespace Flarum\Api\Middleware;
use Closure;
use Flarum\Http\Middleware\IlluminateMiddlewareInterface;
use Flarum\Post\Exception\FloodingException; use Flarum\Post\Exception\FloodingException;
use Psr\Http\Message\ResponseInterface as Response; use Illuminate\Http\Request;
use Psr\Http\Message\ServerRequestInterface as Request; use Symfony\Component\HttpFoundation\Response;
use Psr\Http\Server\MiddlewareInterface as Middleware;
use Psr\Http\Server\RequestHandlerInterface as Handler;
class ThrottleApi implements Middleware class ThrottleApi implements IlluminateMiddlewareInterface
{ {
public function __construct( public function __construct(
protected array $throttlers protected array $throttlers
) { ) {
} }
public function process(Request $request, Handler $handler): Response public function handle(Request $request, Closure $next): Response
{ {
if ($this->throttle($request)) { if ($this->throttle($request)) {
throw new FloodingException; throw new FloodingException;
} }
return $handler->handle($request); return $next($request);
} }
public function throttle(Request $request): bool public function throttle(Request $request): bool

View File

@@ -8,58 +8,45 @@
*/ */
use Flarum\Api\Controller; use Flarum\Api\Controller;
use Flarum\Http\RouteCollection;
use Flarum\Http\RouteHandlerFactory; use Flarum\Http\RouteHandlerFactory;
use Flarum\Http\Router;
return function (Router $router, RouteHandlerFactory $factory) {
return function (RouteCollection $map, RouteHandlerFactory $route) {
// Get forum information // Get forum information
$map->get( $router
'/', ->get('/', $factory->toController(Controller\ShowForumController::class))
'forum.show', ->name('forum.show');
$route->toController(Controller\ShowForumController::class)
);
// List access tokens // List access tokens
$map->get( $router
'/access-tokens', ->get('/access-tokens', $factory->toController(Controller\ListAccessTokensController::class))
'access-tokens.index', ->name('access-tokens.index');
$route->toController(Controller\ListAccessTokensController::class)
);
// Create access token // Create access token
$map->post( $router
'/access-tokens', ->post('/access-tokens', $factory->toController(Controller\CreateAccessTokenController::class))
'access-tokens.create', ->name('access-tokens.create');
$route->toController(Controller\CreateAccessTokenController::class)
);
// Delete access token // Delete access token
$map->delete( $router
'/access-tokens/{id}', ->delete('/access-tokens/{id}', $factory->toController(Controller\DeleteAccessTokenController::class))
'access-tokens.delete', ->name('access-tokens.delete');
$route->toController(Controller\DeleteAccessTokenController::class)
);
// Create authentication token // Create authentication token
$map->post( $router
'/token', ->post('/token', $factory->toController(Controller\CreateTokenController::class))
'token', ->name('token');
$route->toController(Controller\CreateTokenController::class)
);
// Terminate all other sessions // Terminate all other sessions
$map->delete( $router
'/sessions', ->delete('/sessions', $factory->toController(Controller\TerminateAllOtherSessionsController::class))
'sessions.delete', ->name('sessions.delete');
$route->toController(Controller\TerminateAllOtherSessionsController::class)
);
// Send forgot password email // Send forgot password email
$map->post( $router
'/forgot', ->post('/forgot', $factory->toController(Controller\ForgotPasswordController::class))
'forgot', ->name('forgot');
$route->toController(Controller\ForgotPasswordController::class)
);
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@@ -68,60 +55,50 @@ return function (RouteCollection $map, RouteHandlerFactory $route) {
*/ */
// List users // List users
$map->get( $router
'/users', ->get('/users', $factory->toController(Controller\ListUsersController::class))
'users.index', ->name('users.index');
$route->toController(Controller\ListUsersController::class)
);
// Register a user // Register a user
$map->post( $router
'/users', ->post('/users', $factory->toController(Controller\CreateUserController::class))
'users.create', ->name('users.create');
$route->toController(Controller\CreateUserController::class)
);
// Get a single user // Get a single user
$map->get( $router
'/users/{id}', ->get('/users/{id}', $factory->toController(Controller\ShowUserController::class))
'users.show', ->name('users.show')
$route->toController(Controller\ShowUserController::class) ->whereNumber('id');
);
// Edit a user // Edit a user
$map->patch( $router
'/users/{id}', ->patch('/users/{id}', $factory->toController(Controller\UpdateUserController::class))
'users.update', ->name('users.update')
$route->toController(Controller\UpdateUserController::class) ->whereNumber('id');
);
// Delete a user // Delete a user
$map->delete( $router
'/users/{id}', ->delete('/users/{id}', $factory->toController(Controller\DeleteUserController::class))
'users.delete', ->name('users.delete')
$route->toController(Controller\DeleteUserController::class) ->whereNumber('id');
);
// Upload avatar // Upload avatar
$map->post( $router
'/users/{id}/avatar', ->post('/users/{id}/avatar', $factory->toController(Controller\UploadAvatarController::class))
'users.avatar.upload', ->name('users.avatar.upload')
$route->toController(Controller\UploadAvatarController::class) ->whereNumber('id');
);
// Remove avatar // Remove avatar
$map->delete( $router
'/users/{id}/avatar', ->delete('/users/{id}/avatar', $factory->toController(Controller\DeleteAvatarController::class))
'users.avatar.delete', ->name('users.avatar.delete')
$route->toController(Controller\DeleteAvatarController::class) ->whereNumber('id');
);
// send confirmation email // send confirmation email
$map->post( $router
'/users/{id}/send-confirmation', ->post('/users/{id}/send-confirmation', $factory->toController(Controller\SendConfirmationEmailController::class))
'users.confirmation.send', ->name('users.confirmation.send')
$route->toController(Controller\SendConfirmationEmailController::class) ->whereNumber('id');
);
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@@ -130,32 +107,25 @@ return function (RouteCollection $map, RouteHandlerFactory $route) {
*/ */
// List notifications for the current user // List notifications for the current user
$map->get( $router
'/notifications', ->get('/notifications', $factory->toController(Controller\ListNotificationsController::class))
'notifications.index', ->name('notifications.index');
$route->toController(Controller\ListNotificationsController::class)
);
// Mark all notifications as read // Mark all notifications as read
$map->post( $router
'/notifications/read', ->post('/notifications/read', $factory->toController(Controller\ReadAllNotificationsController::class))
'notifications.readAll', ->name('notifications.readAll');
$route->toController(Controller\ReadAllNotificationsController::class)
);
// Mark a single notification as read // Mark a single notification as read
$map->patch( $router
'/notifications/{id}', ->patch('/notifications/{id}', $factory->toController(Controller\UpdateNotificationController::class))
'notifications.update', ->name('notifications.update')
$route->toController(Controller\UpdateNotificationController::class) ->whereNumber('id');
);
// Delete all notifications for the current user. // Delete all notifications for the current user.
$map->delete( $router
'/notifications', ->delete('/notifications', $factory->toController(Controller\DeleteAllNotificationsController::class))
'notifications.deleteAll', ->name('notifications.deleteAll');
$route->toController(Controller\DeleteAllNotificationsController::class)
);
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@@ -164,39 +134,32 @@ return function (RouteCollection $map, RouteHandlerFactory $route) {
*/ */
// List discussions // List discussions
$map->get( $router
'/discussions', ->get('/discussions', $factory->toController(Controller\ListDiscussionsController::class))
'discussions.index', ->name('discussions.index');
$route->toController(Controller\ListDiscussionsController::class)
);
// Create a discussion // Create a discussion
$map->post( $router
'/discussions', ->post('/discussions', $factory->toController(Controller\CreateDiscussionController::class))
'discussions.create', ->name('discussions.create');
$route->toController(Controller\CreateDiscussionController::class)
);
// Show a single discussion // Show a single discussion
$map->get( $router
'/discussions/{id}', ->get('/discussions/{id}', $factory->toController(Controller\ShowDiscussionController::class))
'discussions.show', ->name('discussions.show')
$route->toController(Controller\ShowDiscussionController::class) ->whereNumber('id');
);
// Edit a discussion // Edit a discussion
$map->patch( $router
'/discussions/{id}', ->patch('/discussions/{id}', $factory->toController(Controller\UpdateDiscussionController::class))
'discussions.update', ->name('discussions.update')
$route->toController(Controller\UpdateDiscussionController::class) ->whereNumber('id');
);
// Delete a discussion // Delete a discussion
$map->delete( $router
'/discussions/{id}', ->delete('/discussions/{id}', $factory->toController(Controller\DeleteDiscussionController::class))
'discussions.delete', ->name('discussions.delete')
$route->toController(Controller\DeleteDiscussionController::class) ->whereNumber('id');
);
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@@ -205,39 +168,32 @@ return function (RouteCollection $map, RouteHandlerFactory $route) {
*/ */
// List posts, usually for a discussion // List posts, usually for a discussion
$map->get( $router
'/posts', ->get('/posts', $factory->toController(Controller\ListPostsController::class))
'posts.index', ->name('posts.index');
$route->toController(Controller\ListPostsController::class)
);
// Create a post // Create a post
$map->post( $router
'/posts', ->post('/posts', $factory->toController(Controller\CreatePostController::class))
'posts.create', ->name('posts.create');
$route->toController(Controller\CreatePostController::class)
);
// Show a single or multiple posts by ID // Show a single or multiple posts by ID
$map->get( $router
'/posts/{id}', ->get('/posts/{id}', $factory->toController(Controller\ShowPostController::class))
'posts.show', ->name('posts.show')
$route->toController(Controller\ShowPostController::class) ->whereNumber('id');
);
// Edit a post // Edit a post
$map->patch( $router
'/posts/{id}', ->patch('/posts/{id}', $factory->toController(Controller\UpdatePostController::class))
'posts.update', ->name('posts.update')
$route->toController(Controller\UpdatePostController::class) ->whereNumber('id');
);
// Delete a post // Delete a post
$map->delete( $router
'/posts/{id}', ->delete('/posts/{id}', $factory->toController(Controller\DeletePostController::class))
'posts.delete', ->name('posts.delete')
$route->toController(Controller\DeletePostController::class) ->whereNumber('id');
);
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@@ -246,39 +202,32 @@ return function (RouteCollection $map, RouteHandlerFactory $route) {
*/ */
// List groups // List groups
$map->get( $router
'/groups', ->get('/groups', $factory->toController(Controller\ListGroupsController::class))
'groups.index', ->name('groups.index');
$route->toController(Controller\ListGroupsController::class)
);
// Create a group // Create a group
$map->post( $router
'/groups', ->post('/groups', $factory->toController(Controller\CreateGroupController::class))
'groups.create', ->name('groups.create');
$route->toController(Controller\CreateGroupController::class)
);
// Show a single group // Show a single group
$map->get( $router
'/groups/{id}', ->get('/groups/{id}', $factory->toController(Controller\ShowGroupController::class))
'groups.show', ->name('groups.show')
$route->toController(Controller\ShowGroupController::class) ->whereNumber('id');
);
// Edit a group // Edit a group
$map->patch( $router
'/groups/{id}', ->patch('/groups/{id}', $factory->toController(Controller\UpdateGroupController::class))
'groups.update', ->name('groups.update')
$route->toController(Controller\UpdateGroupController::class) ->whereNumber('id');
);
// Delete a group // Delete a group
$map->delete( $router
'/groups/{id}', ->delete('/groups/{id}', $factory->toController(Controller\DeleteGroupController::class))
'groups.delete', ->name('groups.delete')
$route->toController(Controller\DeleteGroupController::class) ->whereNumber('id');
);
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@@ -287,86 +236,63 @@ return function (RouteCollection $map, RouteHandlerFactory $route) {
*/ */
// Toggle an extension // Toggle an extension
$map->patch( $router
'/extensions/{name}', ->patch('/extensions/{name}', $factory->toController(Controller\UpdateExtensionController::class))
'extensions.update', ->name('extensions.update');
$route->toController(Controller\UpdateExtensionController::class)
);
// Uninstall an extension // Uninstall an extension
$map->delete( $router
'/extensions/{name}', ->delete('/extensions/{name}', $factory->toController(Controller\UninstallExtensionController::class))
'extensions.delete', ->name('extensions.delete');
$route->toController(Controller\UninstallExtensionController::class)
);
// Get readme for an extension // Get readme for an extension
$map->get( $router
'/extension-readmes/{name}', ->get('/extension-readmes/{name}', $factory->toController(Controller\ShowExtensionReadmeController::class))
'extension-readmes.show', ->name('extension-readmes.show');
$route->toController(Controller\ShowExtensionReadmeController::class)
);
// Update settings // Update settings
$map->post( $router
'/settings', ->post('/settings', $factory->toController(Controller\SetSettingsController::class))
'settings', ->name('settings');
$route->toController(Controller\SetSettingsController::class)
);
// Update a permission // Update a permission
$map->post( $router
'/permission', ->post('/permission', $factory->toController(Controller\SetPermissionController::class))
'permission', ->name('permission');
$route->toController(Controller\SetPermissionController::class)
);
// Upload a logo // Upload a logo
$map->post( $router
'/logo', ->post('/logo', $factory->toController(Controller\UploadLogoController::class))
'logo', ->name('logo');
$route->toController(Controller\UploadLogoController::class)
);
// Remove the logo // Remove the logo
$map->delete( $router
'/logo', ->delete('/logo', $factory->toController(Controller\DeleteLogoController::class))
'logo.delete', ->name('logo.delete');
$route->toController(Controller\DeleteLogoController::class)
);
// Upload a favicon // Upload a favicon
$map->post( $router
'/favicon', ->post('/favicon', $factory->toController(Controller\UploadFaviconController::class))
'favicon', ->name('favicon');
$route->toController(Controller\UploadFaviconController::class)
);
// Remove the favicon // Remove the favicon
$map->delete( $router
'/favicon', ->delete('/favicon', $factory->toController(Controller\DeleteFaviconController::class))
'favicon.delete', ->name('favicon.delete');
$route->toController(Controller\DeleteFaviconController::class)
);
// Clear the cache // Clear the cache
$map->delete( $router
'/cache', ->delete('/cache', $factory->toController(Controller\ClearCacheController::class))
'cache.clear', ->name('cache.clear');
$route->toController(Controller\ClearCacheController::class)
);
// List available mail drivers, available fields and validation status // List available mail drivers, available fields and validation status
$map->get( $router
'/mail/settings', ->get('/mail/settings', $factory->toController(Controller\ShowMailSettingsController::class))
'mailSettings.index', ->name('mailSettings.index');
$route->toController(Controller\ShowMailSettingsController::class)
);
// Send test mail post // Send test mail post
$map->post( $router
'/mail/test', ->post('/mail/test', $factory->toController(Controller\SendTestMailController::class))
'mailTest', ->name('mailTest');
$route->toController(Controller\SendTestMailController::class)
);
}; };

View File

@@ -13,6 +13,7 @@ use Flarum\Extension\Event\Disabled;
use Flarum\Extension\Event\Enabled; use Flarum\Extension\Event\Enabled;
use Flarum\Formatter\Formatter; use Flarum\Formatter\Formatter;
use Flarum\Foundation\AbstractServiceProvider; use Flarum\Foundation\AbstractServiceProvider;
use Flarum\Foundation\Config;
use Flarum\Foundation\ErrorHandling\Registry; use Flarum\Foundation\ErrorHandling\Registry;
use Flarum\Foundation\ErrorHandling\Reporter; use Flarum\Foundation\ErrorHandling\Reporter;
use Flarum\Foundation\ErrorHandling\ViewFormatter; use Flarum\Foundation\ErrorHandling\ViewFormatter;
@@ -24,9 +25,8 @@ use Flarum\Frontend\Assets;
use Flarum\Frontend\Compiler\Source\SourceCollector; use Flarum\Frontend\Compiler\Source\SourceCollector;
use Flarum\Frontend\RecompileFrontendAssets; use Flarum\Frontend\RecompileFrontendAssets;
use Flarum\Http\Middleware as HttpMiddleware; use Flarum\Http\Middleware as HttpMiddleware;
use Flarum\Http\RouteCollection;
use Flarum\Http\RouteHandlerFactory; use Flarum\Http\RouteHandlerFactory;
use Flarum\Http\UrlGenerator; use Flarum\Http\Router;
use Flarum\Locale\LocaleManager; use Flarum\Locale\LocaleManager;
use Flarum\Settings\Event\Saved; use Flarum\Settings\Event\Saved;
use Flarum\Settings\Event\Saving; use Flarum\Settings\Event\Saving;
@@ -34,39 +34,41 @@ use Flarum\Settings\SettingsRepositoryInterface;
use Illuminate\Contracts\Container\Container; use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Contracts\View\Factory; use Illuminate\Contracts\View\Factory;
use Laminas\Stratigility\MiddlewarePipe;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
class ForumServiceProvider extends AbstractServiceProvider class ForumServiceProvider extends AbstractServiceProvider
{ {
public function register(): void public function register(): void
{ {
$this->container->extend(UrlGenerator::class, function (UrlGenerator $url, Container $container) { $this->booted(function (Container $container) {
return $url->addCollection('forum', $container->make('flarum.forum.routes')); /** @var Router $router */
}); $router = $container->make(Router::class);
/** @var Config $config */
$config = $container->make(Config::class);
$this->container->singleton('flarum.forum.routes', function (Container $container) { $router->middlewareGroup('forum', $container->make('flarum.forum.middleware'));
$routes = new RouteCollection;
$this->populateRoutes($routes, $container);
return $routes; $factory = $container->make(RouteHandlerFactory::class);
});
$this->container->afterResolving('flarum.forum.routes', function (RouteCollection $routes, Container $container) { $router->middleware('forum')->prefix($config->path('forum'))->group(
$this->setDefaultRoute($routes, $container); fn (Router $router) => (include __DIR__.'/routes.php')($router, $factory)
);
$this->setDefaultRoute(
$router,
$container->make(SettingsRepositoryInterface::class)
);
}); });
$this->container->singleton('flarum.forum.middleware', function () { $this->container->singleton('flarum.forum.middleware', function () {
return [ return [
HttpMiddleware\InjectActorReference::class, HttpMiddleware\InjectActorReference::class,
'flarum.forum.error_handler', 'flarum.forum.error_handler',
HttpMiddleware\ParseJsonBody::class,
HttpMiddleware\CollectGarbage::class, HttpMiddleware\CollectGarbage::class,
HttpMiddleware\StartSession::class, HttpMiddleware\StartSession::class,
HttpMiddleware\RememberFromCookie::class, HttpMiddleware\RememberFromCookie::class,
HttpMiddleware\AuthenticateWithSession::class, HttpMiddleware\AuthenticateWithSession::class,
HttpMiddleware\SetLocale::class, HttpMiddleware\SetLocale::class,
'flarum.forum.route_resolver',
HttpMiddleware\CheckCsrfToken::class, HttpMiddleware\CheckCsrfToken::class,
HttpMiddleware\ShareErrorsFromSession::class, HttpMiddleware\ShareErrorsFromSession::class,
HttpMiddleware\FlarumPromotionHeader::class, HttpMiddleware\FlarumPromotionHeader::class,
@@ -83,22 +85,6 @@ class ForumServiceProvider extends AbstractServiceProvider
); );
}); });
$this->container->bind('flarum.forum.route_resolver', function (Container $container) {
return new HttpMiddleware\ResolveRoute($container->make('flarum.forum.routes'));
});
$this->container->singleton('flarum.forum.handler', function (Container $container) {
$pipe = new MiddlewarePipe;
foreach ($container->make('flarum.forum.middleware') as $middleware) {
$pipe->pipe($container->make($middleware));
}
$pipe->pipe(new HttpMiddleware\ExecuteRoute());
return $pipe;
});
$this->container->bind('flarum.assets.forum', function (Container $container) { $this->container->bind('flarum.assets.forum', function (Container $container) {
/** @var Assets $assets */ /** @var Assets $assets */
$assets = $container->make('flarum.assets.factory')('forum'); $assets = $container->make('flarum.assets.factory')('forum');
@@ -194,29 +180,10 @@ class ForumServiceProvider extends AbstractServiceProvider
); );
} }
protected function populateRoutes(RouteCollection $routes, Container $container): void protected function setDefaultRoute(Router $router, SettingsRepositoryInterface $settings): void
{ {
$factory = $container->make(RouteHandlerFactory::class); $defaultRoute = $settings->get('default_route');
$action = $router->getRoutes()->getByName($defaultRoute)?->getAction() ?? 'index';
$callback = include __DIR__.'/routes.php'; $router->get('/', $action)->name('default');
$callback($routes, $factory);
}
protected function setDefaultRoute(RouteCollection $routes, Container $container): void
{
$factory = $container->make(RouteHandlerFactory::class);
$defaultRoute = $container->make('flarum.settings')->get('default_route');
if (isset($routes->getRouteData()[0]['GET'][$defaultRoute]['handler'])) {
$toDefaultController = $routes->getRouteData()[0]['GET'][$defaultRoute]['handler'];
} else {
$toDefaultController = $factory->toForum(Content\Index::class);
}
$routes->get(
'/',
'default',
$toDefaultController
);
} }
} }

View File

@@ -9,85 +9,64 @@
use Flarum\Forum\Content; use Flarum\Forum\Content;
use Flarum\Forum\Controller; use Flarum\Forum\Controller;
use Flarum\Http\RouteCollection;
use Flarum\Http\RouteHandlerFactory; use Flarum\Http\RouteHandlerFactory;
use Flarum\Http\Router;
return function (RouteCollection $map, RouteHandlerFactory $route) { return function (Router $router, RouteHandlerFactory $factory) {
$map->get(
'/all',
'index',
$route->toForum(Content\Index::class)
);
$map->get( $router
'/d/{id:\d+(?:-[^/]*)?}[/{near:[^/]*}]', ->get('/all', $factory->toForum(Content\Index::class))
'discussion', ->name('index');
$route->toForum(Content\Discussion::class)
);
$map->get( $router
'/u/{username}[/{filter:[^/]*}]', ->get('/d/{id}/{near?}', $factory->toForum(Content\Discussion::class))
'user', ->where('id', '\d+(?:-[^/]*)?')
$route->toForum(Content\User::class) ->where('near', '[^/]*')
); ->name('discussion');
$map->get( $router
'/settings', ->get('/u/{username}/{filter?}', $factory->toForum(Content\User::class))
'settings', ->where('filter', '[^/]*')
$route->toForum(Content\AssertRegistered::class) ->name('user');
);
$map->get( $router
'/notifications', ->get('/settings', $factory->toForum(Content\AssertRegistered::class))
'notifications', ->name('settings');
$route->toForum(Content\AssertRegistered::class)
);
$map->get( $router
'/logout', ->get('/notifications', $factory->toForum(Content\AssertRegistered::class))
'logout', ->name('notifications');
$route->toController(Controller\LogOutController::class)
);
$map->post( $router
'/global-logout', ->get('/logout', $factory->toController(Controller\LogOutController::class))
'globalLogout', ->name('logout');
$route->toController(Controller\GlobalLogOutController::class)
);
$map->post( $router
'/login', ->post('/global-logout', $factory->toController(Controller\GlobalLogOutController::class))
'login', ->name('globalLogout');
$route->toController(Controller\LogInController::class)
);
$map->post( $router
'/register', ->post('/login', $factory->toController(Controller\LogInController::class))
'register', ->name('login');
$route->toController(Controller\RegisterController::class)
);
$map->get( $router
'/confirm/{token}', ->post('/register', $factory->toController(Controller\RegisterController::class))
'confirmEmail', ->name('register');
$route->toController(Controller\ConfirmEmailViewController::class),
);
$map->post( $router
'/confirm/{token}', ->get('/confirm/{token}', $factory->toController(Controller\ConfirmEmailViewController::class))
'confirmEmail.submit', ->name('confirmEmail');
$route->toController(Controller\ConfirmEmailController::class),
);
$map->get( $router
'/reset/{token}', ->post('/confirm/{token}', $factory->toController(Controller\ConfirmEmailController::class))
'resetPassword', ->name('confirmEmail.submit');
$route->toController(Controller\ResetPasswordController::class)
); $router
->get('/reset/{token}', $factory->toController(Controller\ResetPasswordController::class))
->name('resetPassword');
$router
->post('/reset', $factory->toController(Controller\SavePasswordController::class))
->name('savePassword');
$map->post(
'/reset',
'savePassword',
$route->toController(Controller\SavePasswordController::class)
);
}; };

View File

@@ -10,14 +10,13 @@
namespace Flarum\Foundation; namespace Flarum\Foundation;
use Illuminate\Contracts\Container\Container; use Illuminate\Contracts\Container\Container;
use Psr\Http\Server\RequestHandlerInterface;
use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Command\Command;
interface AppInterface interface AppInterface
{ {
public function getContainer(): Container; public function getContainer(): Container;
public function getRequestHandler(): RequestHandlerInterface; public function getMiddlewareStack(): array;
/** /**
* @return Command[] * @return Command[]

View File

@@ -10,6 +10,7 @@
namespace Flarum\Foundation; namespace Flarum\Foundation;
use Flarum\Foundation\Concerns\InteractsWithLaravel; use Flarum\Foundation\Concerns\InteractsWithLaravel;
use Flarum\Http\RoutingServiceProvider;
use Illuminate\Container\Container as IlluminateContainer; use Illuminate\Container\Container as IlluminateContainer;
use Illuminate\Contracts\Foundation\Application as LaravelApplication; use Illuminate\Contracts\Foundation\Application as LaravelApplication;
use Illuminate\Events\EventServiceProvider; use Illuminate\Events\EventServiceProvider;
@@ -69,21 +70,15 @@ class Application extends IlluminateContainer implements LaravelApplication
IlluminateContainer::setInstance($this); IlluminateContainer::setInstance($this);
$this->instance('app', $this); $this->instance('app', $this);
$this->alias('app', IlluminateContainer::class);
$this->instance('container', $this); $this->instance('container', $this);
$this->alias('container', IlluminateContainer::class);
$this->instance('flarum', $this); $this->instance('flarum', $this);
$this->alias('flarum', self::class);
$this->instance('flarum.paths', $this->paths); $this->instance('flarum.paths', $this->paths);
$this->alias('flarum.paths', Paths::class);
} }
protected function registerBaseServiceProviders(): void protected function registerBaseServiceProviders(): void
{ {
$this->register(new EventServiceProvider($this)); $this->register(new EventServiceProvider($this));
$this->register(new RoutingServiceProvider($this));
} }
public function register($provider, $force = false): ServiceProvider public function register($provider, $force = false): ServiceProvider
@@ -166,13 +161,15 @@ class Application extends IlluminateContainer implements LaravelApplication
$this->fireAppCallbacks($this->bootedCallbacks); $this->fireAppCallbacks($this->bootedCallbacks);
} }
protected function bootProvider(ServiceProvider $provider): mixed protected function bootProvider(ServiceProvider $provider): void
{ {
$provider->callBootingCallbacks();
if (method_exists($provider, 'boot')) { if (method_exists($provider, 'boot')) {
return $this->call([$provider, 'boot']); $this->call([$provider, 'boot']);
} }
return null; $provider->callBootedCallbacks();
} }
public function booting(mixed $callback): void public function booting(mixed $callback): void
@@ -199,11 +196,12 @@ class Application extends IlluminateContainer implements LaravelApplication
public function registerCoreContainerAliases(): void public function registerCoreContainerAliases(): void
{ {
$aliases = [ $aliases = [
'app' => [\Illuminate\Contracts\Container\Container::class, \Illuminate\Contracts\Foundation\Application::class, \Psr\Container\ContainerInterface::class], 'app' => [\Illuminate\Contracts\Container\Container::class, \Illuminate\Contracts\Foundation\Application::class, \Psr\Container\ContainerInterface::class],
'blade.compiler' => [\Illuminate\View\Compilers\BladeCompiler::class], 'blade.compiler' => [\Illuminate\View\Compilers\BladeCompiler::class],
'cache' => [\Illuminate\Cache\CacheManager::class, \Illuminate\Contracts\Cache\Factory::class], 'cache' => [\Illuminate\Cache\CacheManager::class, \Illuminate\Contracts\Cache\Factory::class],
'cache.store' => [\Illuminate\Cache\Repository::class, \Illuminate\Contracts\Cache\Repository::class], 'cache.store' => [\Illuminate\Cache\Repository::class, \Illuminate\Contracts\Cache\Repository::class],
'config' => [\Illuminate\Config\Repository::class, \Illuminate\Contracts\Config\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\DatabaseManager::class],
'db.connection' => [\Illuminate\Database\Connection::class, \Illuminate\Database\ConnectionInterface::class], 'db.connection' => [\Illuminate\Database\Connection::class, \Illuminate\Database\ConnectionInterface::class],
'events' => [\Illuminate\Events\Dispatcher::class, \Illuminate\Contracts\Events\Dispatcher::class], 'events' => [\Illuminate\Events\Dispatcher::class, \Illuminate\Contracts\Events\Dispatcher::class],
@@ -211,8 +209,13 @@ class Application extends IlluminateContainer implements LaravelApplication
'filesystem' => [\Illuminate\Filesystem\FilesystemManager::class, \Illuminate\Contracts\Filesystem\Factory::class], 'filesystem' => [\Illuminate\Filesystem\FilesystemManager::class, \Illuminate\Contracts\Filesystem\Factory::class],
'filesystem.disk' => [\Illuminate\Contracts\Filesystem\Filesystem::class], 'filesystem.disk' => [\Illuminate\Contracts\Filesystem\Filesystem::class],
'filesystem.cloud' => [\Illuminate\Contracts\Filesystem\Cloud::class], 'filesystem.cloud' => [\Illuminate\Contracts\Filesystem\Cloud::class],
'flarum' => [\Illuminate\Contracts\Container\Container::class, \Illuminate\Contracts\Foundation\Application::class, \Psr\Container\ContainerInterface::class, self::class],
'flarum.paths' => [Paths::class],
'hash' => [\Illuminate\Contracts\Hashing\Hasher::class], 'hash' => [\Illuminate\Contracts\Hashing\Hasher::class],
'mailer' => [\Illuminate\Mail\Mailer::class, \Illuminate\Contracts\Mail\Mailer::class, \Illuminate\Contracts\Mail\MailQueue::class], 'mailer' => [\Illuminate\Mail\Mailer::class, \Illuminate\Contracts\Mail\Mailer::class, \Illuminate\Contracts\Mail\MailQueue::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],
'validator' => [\Illuminate\Validation\Factory::class, \Illuminate\Contracts\Validation\Factory::class], 'validator' => [\Illuminate\Validation\Factory::class, \Illuminate\Contracts\Validation\Factory::class],
'view' => [\Illuminate\View\Factory::class, \Illuminate\Contracts\View\Factory::class], 'view' => [\Illuminate\View\Factory::class, \Illuminate\Contracts\View\Factory::class],
]; ];

View File

@@ -39,6 +39,15 @@ class Config implements ArrayAccess
return $this->data['offline'] ?? false; return $this->data['offline'] ?? false;
} }
public function path(string $frontend): string
{
return match(true) {
isset($this->data['paths'][$frontend]) => $this->data['paths'][$frontend],
$frontend === 'forum' => '/',
default => $frontend,
};
}
private function requireKeys(mixed ...$keys): void private function requireKeys(mixed ...$keys): void
{ {
foreach ($keys as $key) { foreach ($keys as $key) {

View File

@@ -33,29 +33,16 @@ class InstalledApp implements AppInterface
return $this->container; return $this->container;
} }
public function getRequestHandler(): RequestHandlerInterface public function getMiddlewareStack(): array
{ {
if ($this->config->inMaintenanceMode()) { // if ($this->config->inMaintenanceMode()) {
return $this->container->make('flarum.maintenance.handler'); // return $this->container->make('flarum.maintenance.handler');
} elseif ($this->needsUpdate()) { // }
return $this->getUpdaterHandler();
}
$pipe = new MiddlewarePipe; return match ($this->needsUpdate()) {
true => $this->getUpdaterMiddlewareStack(),
$pipe->pipe(new HttpMiddleware\ProcessIp()); false => $this->getStandardMiddlewareStack(),
$pipe->pipe(new BasePath($this->basePath())); };
$pipe->pipe(new OriginalMessages);
$pipe->pipe(
new BasePathRouter([
$this->subPath('api') => 'flarum.api.handler',
$this->subPath('admin') => 'flarum.admin.handler',
'/' => 'flarum.forum.handler',
])
);
$pipe->pipe(new RequestHandler($this->container));
return $pipe;
} }
protected function needsUpdate(): bool protected function needsUpdate(): bool
@@ -66,16 +53,19 @@ class InstalledApp implements AppInterface
return $version !== Application::VERSION; return $version !== Application::VERSION;
} }
protected function getUpdaterHandler(): RequestHandlerInterface|MiddlewarePipe protected function getUpdaterMiddlewareStack(): array
{ {
$pipe = new MiddlewarePipe; return [
$pipe->pipe(new BasePath($this->basePath())); new BasePath($this->basePath()),
$pipe->pipe( ];
new HttpMiddleware\ResolveRoute($this->container->make('flarum.update.routes')) }
);
$pipe->pipe(new HttpMiddleware\ExecuteRoute());
return $pipe; protected function getStandardMiddlewareStack(): array
{
return [
new BasePath($this->basePath()),
new OriginalMessages,
];
} }
protected function basePath(): string protected function basePath(): string
@@ -83,11 +73,6 @@ class InstalledApp implements AppInterface
return $this->config->url()->getPath() ?: '/'; return $this->config->url()->getPath() ?: '/';
} }
protected function subPath(string $pathName): string
{
return '/'.($this->config['paths'][$pathName] ?? $pathName);
}
public function getConsoleCommands(): array public function getConsoleCommands(): array
{ {
return array_map(function ($command) { return array_map(function ($command) {

View File

@@ -9,22 +9,22 @@
namespace Flarum\Foundation; namespace Flarum\Foundation;
use Flarum\Http\Controller\AbstractController;
use Illuminate\Http\Request;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Laminas\Diactoros\Response\HtmlResponse; use Laminas\Diactoros\Response\HtmlResponse;
use Laminas\Diactoros\Response\JsonResponse; use Laminas\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Tobscure\JsonApi\Document; use Tobscure\JsonApi\Document;
class MaintenanceModeHandler implements RequestHandlerInterface class MaintenanceModeHandler extends AbstractController
{ {
const MESSAGE = 'Currently down for maintenance. Please come back later.'; const MESSAGE = 'Currently down for maintenance. Please come back later.';
/** /**
* Handle the request and return a response. * Handle the request and return a response.
*/ */
public function handle(ServerRequestInterface $request): ResponseInterface public function __invoke(Request $request): ResponseInterface
{ {
// Special handling for API requests: they get a proper API response // Special handling for API requests: they get a proper API response
if ($this->isApiRequest($request)) { if ($this->isApiRequest($request)) {
@@ -35,10 +35,10 @@ class MaintenanceModeHandler implements RequestHandlerInterface
return new HtmlResponse(self::MESSAGE, 503); return new HtmlResponse(self::MESSAGE, 503);
} }
private function isApiRequest(ServerRequestInterface $request): bool private function isApiRequest(Request $request): bool
{ {
return Str::contains( return Str::contains(
$request->getHeaderLine('Accept'), $request->header('Accept'),
'application/vnd.api+json' 'application/vnd.api+json'
); );
} }

View File

@@ -15,9 +15,8 @@ use Flarum\Database\ScopeVisibilityTrait;
use Flarum\User\User; use Flarum\User\User;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Arr; use Illuminate\Http\Request;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Psr\Http\Message\ServerRequestInterface;
/** /**
* @property int $id * @property int $id
@@ -96,7 +95,7 @@ class AccessToken extends AbstractModel
* Update the time of last usage of a token. * Update the time of last usage of a token.
* If a request object is provided, the IP address and User Agent will also be logged. * If a request object is provided, the IP address and User Agent will also be logged.
*/ */
public function touch($attribute = null, ServerRequestInterface $request = null): bool public function touch($attribute = null, Request $request = null): bool
{ {
$now = Carbon::now(); $now = Carbon::now();
@@ -105,11 +104,11 @@ class AccessToken extends AbstractModel
} }
if ($request) { if ($request) {
$this->last_ip_address = $request->getAttribute('ipAddress'); $this->last_ip_address = $request->ip();
// We truncate user agent so it fits in the database column // We truncate user agent so it fits in the database column
// The length is hard-coded as the column length // The length is hard-coded as the column length
// It seems like MySQL or Laravel already truncates values, but we'll play safe and do it ourselves // It seems like MySQL or Laravel already truncates values, but we'll play safe and do it ourselves
$agent = Arr::get($request->getServerParams(), 'HTTP_USER_AGENT'); $agent = $request->server->get('HTTP_USER_AGENT');
$this->last_user_agent = substr($agent ?? '', 0, 255); $this->last_user_agent = substr($agent ?? '', 0, 255);
} else { } else {
// If no request is provided, we set the values back to null // If no request is provided, we set the values back to null

View File

@@ -9,9 +9,9 @@
namespace Flarum\Http; namespace Flarum\Http;
use Dflydev\FigCookies\Modifier\SameSite; use DateTime;
use Dflydev\FigCookies\SetCookie;
use Flarum\Foundation\Config; use Flarum\Foundation\Config;
use Symfony\Component\HttpFoundation\Cookie;
class CookieFactory class CookieFactory
{ {
@@ -40,16 +40,14 @@ class CookieFactory
* This method returns a cookie instance for use with the Set-Cookie HTTP header. * This method returns a cookie instance for use with the Set-Cookie HTTP header.
* It will be pre-configured according to Flarum's base URL and protocol. * It will be pre-configured according to Flarum's base URL and protocol.
*/ */
public function make(string $name, ?string $value = null, ?int $maxAge = null): SetCookie public function make(string $name, ?string $value = null, ?int $maxAge = null): Cookie
{ {
$cookie = SetCookie::create($this->getName($name), $value); $cookie = Cookie::create($this->getName($name), $value);
// Make sure we send both the MaxAge and Expires parameters (the former // Make sure we send both the MaxAge and Expires parameters (the former
// is not supported by all browser versions) // is not supported by all browser versions)
if ($maxAge) { if ($maxAge) {
$cookie = $cookie $cookie = $cookie->withExpires(time() + $maxAge);
->withMaxAge($maxAge)
->withExpires(time() + $maxAge);
} }
if ($this->domain != null) { if ($this->domain != null) {
@@ -57,20 +55,20 @@ class CookieFactory
} }
// Explicitly set SameSite value, use sensible default if no value provided // Explicitly set SameSite value, use sensible default if no value provided
$cookie = $cookie->withSameSite(SameSite::{$this->samesite ?? 'lax'}()); $cookie = $cookie->withSameSite($this->samesite ?? 'lax');
return $cookie return $cookie
->withPath($this->path) ->withPath($this->path)
->withSecure($this->secure) ->withSecure($this->secure)
->withHttpOnly(true); ->withHttpOnly();
} }
/** /**
* Make an expired cookie instance. * Make an expired cookie instance.
*/ */
public function expire(string $name): SetCookie public function expire(string $name): Cookie
{ {
return $this->make($name)->expire(); return $this->make($name)->withExpires(new DateTime('-5 years'));
} }
/** /**

View File

@@ -1,29 +0,0 @@
<?php
/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\Http\Middleware;
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 ExecuteRoute implements Middleware
{
/**
* Executes the route handler resolved in ResolveRoute.
*/
public function process(Request $request, Handler $handler): Response
{
$handler = $request->getAttribute('routeHandler');
$parameters = $request->getAttribute('routeParameters');
return $handler($request, $parameters);
}
}

View File

@@ -1,30 +0,0 @@
<?php
/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\Http\Middleware;
use Illuminate\Support\Str;
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 ParseJsonBody implements Middleware
{
public function process(Request $request, Handler $handler): Response
{
if (Str::contains($request->getHeaderLine('content-type'), 'json')) {
$input = json_decode($request->getBody(), true);
$request = $request->withParsedBody($input ?: []);
}
return $handler->handle($request);
}
}

View File

@@ -1,26 +0,0 @@
<?php
/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\Http\Middleware;
use Illuminate\Support\Arr;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface as Middleware;
use Psr\Http\Server\RequestHandlerInterface;
class ProcessIp implements Middleware
{
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$ipAddress = Arr::get($request->getServerParams(), 'REMOTE_ADDR', '127.0.0.1');
return $handler->handle($request->withAttribute('ipAddress', $ipAddress));
}
}

View File

@@ -1,66 +0,0 @@
<?php
/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\Http\Middleware;
use FastRoute\Dispatcher;
use Flarum\Http\Exception\MethodNotAllowedException;
use Flarum\Http\Exception\RouteNotFoundException;
use Flarum\Http\RouteCollection;
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 ResolveRoute implements Middleware
{
protected ?Dispatcher\GroupCountBased $dispatcher = null;
public function __construct(
protected RouteCollection $routes
) {
}
/**
* Resolve the given request from our route collection.
*
* @throws MethodNotAllowedException
* @throws RouteNotFoundException
*/
public function process(Request $request, Handler $handler): Response
{
$method = $request->getMethod();
$uri = $request->getUri()->getPath() ?: '/';
$routeInfo = $this->getDispatcher()->dispatch($method, $uri);
switch ($routeInfo[0]) {
case Dispatcher::NOT_FOUND:
throw new RouteNotFoundException($uri);
case Dispatcher::METHOD_NOT_ALLOWED:
throw new MethodNotAllowedException($method);
default:
$request = $request
->withAttribute('routeName', $routeInfo[1]['name'])
->withAttribute('routeHandler', $routeInfo[1]['handler'])
->withAttribute('routeParameters', $routeInfo[2]);
return $handler->handle($request);
}
}
protected function getDispatcher(): Dispatcher\GroupCountBased
{
if (! isset($this->dispatcher)) {
$this->dispatcher = new Dispatcher\GroupCountBased($this->routes->getRouteData());
}
return $this->dispatcher;
}
}

View File

@@ -9,8 +9,7 @@
namespace Flarum\Http; namespace Flarum\Http;
use Dflydev\FigCookies\FigResponseCookies; use Symfony\Component\HttpFoundation\Response;
use Psr\Http\Message\ResponseInterface;
class Rememberer class Rememberer
{ {
@@ -24,19 +23,21 @@ class Rememberer
/** /**
* Sets the remember cookie on a response. * Sets the remember cookie on a response.
*/ */
public function remember(ResponseInterface $response, RememberAccessToken $token): ResponseInterface public function remember(Response $response, RememberAccessToken $token): Response
{ {
return FigResponseCookies::set( $response->headers->setCookie(
$response,
$this->cookie->make(self::COOKIE_NAME, $token->token, RememberAccessToken::rememberCookieLifeTime()) $this->cookie->make(self::COOKIE_NAME, $token->token, RememberAccessToken::rememberCookieLifeTime())
); );
return $response;
} }
public function forget(ResponseInterface $response): ResponseInterface public function forget(Response $response): Response
{ {
return FigResponseCookies::set( $response->headers->setCookie(
$response,
$this->cookie->expire(self::COOKIE_NAME) $this->cookie->expire(self::COOKIE_NAME)
); );
return $response;
} }
} }

View File

@@ -10,29 +10,78 @@
namespace Flarum\Http; namespace Flarum\Http;
use Flarum\User\User; use Flarum\User\User;
use Psr\Http\Message\ServerRequestInterface as Request; use Illuminate\Http\Request as IlluminateRequest;
use Laminas\Diactoros\ResponseFactory;
use Laminas\Diactoros\ServerRequestFactory;
use Laminas\Diactoros\StreamFactory;
use Laminas\Diactoros\UploadedFileFactory;
use Psr\Http\Message\ServerRequestInterface;
use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory;
use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory;
use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
class RequestUtil class RequestUtil
{ {
public static function getActor(Request $request): User public static function getActor(ServerRequestInterface|SymfonyRequest $request): User
{ {
return $request->getAttribute('actorReference')->getActor(); return self::getActorReference($request)->getActor();
} }
public static function withActor(Request $request, User $actor): Request public static function withActor(ServerRequestInterface|SymfonyRequest $request, User $actor): ServerRequestInterface|SymfonyRequest
{ {
$actorReference = $request->getAttribute('actorReference'); $actorReference = self::getActorReference($request);
if (! $actorReference) { if (! $actorReference) {
$actorReference = new ActorReference; $actorReference = new ActorReference;
$request = $request->withAttribute('actorReference', $actorReference); $request = self::setActorReference($request, $actorReference);
} }
$actorReference->setActor($actor); $actorReference->setActor($actor);
// @deprecated in 1.0 return $request;
$request = $request->withAttribute('actor', $actor); }
private static function setActorReference(ServerRequestInterface|SymfonyRequest $request, ActorReference $reference): ServerRequestInterface|SymfonyRequest
{
if ($request instanceof ServerRequestInterface) {
$request = $request->withAttribute('actorReference', $reference);
} else {
$request->attributes->set('actorReference', $reference);
}
return $request; return $request;
} }
private static function getActorReference(ServerRequestInterface|SymfonyRequest $request): ?ActorReference
{
if ($request instanceof ServerRequestInterface) {
return $request->getAttribute('actorReference');
}
return $request->attributes->get('actorReference');
}
public static function toIlluminate(ServerRequestInterface $request): IlluminateRequest
{
$httpFoundationFactory = new HttpFoundationFactory();
return IlluminateRequest::createFromBase(
$httpFoundationFactory->createRequest($request)
);
}
public static function toPsr7(SymfonyRequest $request): ServerRequestInterface
{
$psrHttpFactory = new PsrHttpFactory(
new ServerRequestFactory(), new StreamFactory(), new UploadedFileFactory(), new ResponseFactory()
);
return $psrHttpFactory->createRequest($request);
}
public static function responseToSymfony(\Psr\Http\Message\ResponseInterface $response): SymfonyResponse
{
return (new HttpFoundationFactory())->createResponse($response);
}
} }

View File

@@ -9,142 +9,28 @@
namespace Flarum\Http; namespace Flarum\Http;
use FastRoute\DataGenerator; use Illuminate\Routing\RouteCollection as IlluminateRouteCollection;
use FastRoute\RouteParser;
use Illuminate\Support\Arr;
/** /**
* @internal * @internal
*/ */
class RouteCollection class RouteCollection extends IlluminateRouteCollection
{ {
protected array $reverse = []; public function forgetNamedRoute($name): void
protected DataGenerator $dataGenerator;
protected RouteParser $routeParser;
protected array $routes = [];
protected array $pendingRoutes = [];
public function __construct()
{ {
$this->dataGenerator = new DataGenerator\GroupCountBased; $route = $this->getByName($name);
$this->routeParser = new RouteParser\Std;
}
public function get(string $path, string $name, callable|string $handler): self if (! $route) {
{ return;
return $this->addRoute('GET', $path, $name, $handler);
}
public function post(string $path, string $name, callable|string $handler): self
{
return $this->addRoute('POST', $path, $name, $handler);
}
public function put(string $path, string $name, callable|string $handler): self
{
return $this->addRoute('PUT', $path, $name, $handler);
}
public function patch(string $path, string $name, callable|string $handler): self
{
return $this->addRoute('PATCH', $path, $name, $handler);
}
public function delete(string $path, string $name, callable|string $handler): self
{
return $this->addRoute('DELETE', $path, $name, $handler);
}
public function addRoute(string $method, string $path, string $name, callable|string $handler): self
{
if (isset($this->routes[$name])) {
throw new \RuntimeException("Route $name already exists");
} }
$this->routes[$name] = $this->pendingRoutes[$name] = compact('method', 'path', 'handler'); // Remove the quick lookup.
unset($this->nameList[$name]);
return $this; // Remove from the routes.
} foreach ($route->methods() as $method) {
unset($this->routes[$method][$route->getDomain().$route->uri()]);
public function removeRoute(string $name): self unset($this->allRoutes[$method.$route->getDomain().$route->uri()]);
{
unset($this->routes[$name], $this->pendingRoutes[$name]);
return $this;
}
protected function applyRoutes(): void
{
foreach ($this->pendingRoutes as $name => $route) {
$routeDatas = $this->routeParser->parse($route['path']);
foreach ($routeDatas as $routeData) {
$this->dataGenerator->addRoute($route['method'], $routeData, ['name' => $name, 'handler' => $route['handler']]);
}
$this->reverse[$name] = $routeDatas;
} }
$this->pendingRoutes = [];
}
public function getRoutes(): array
{
return $this->routes;
}
public function getRouteData(): array
{
if (! empty($this->pendingRoutes)) {
$this->applyRoutes();
}
return $this->dataGenerator->getData();
}
protected function fixPathPart(mixed $part, array $parameters, string $routeName): string
{
if (! is_array($part)) {
return $part;
}
if (! array_key_exists($part[0], $parameters)) {
throw new \InvalidArgumentException("Could not generate URL for route '$routeName': no value provided for required part '$part[0]'.");
}
return $parameters[$part[0]];
}
public function getPath(string $name, array $parameters = []): string
{
if (! empty($this->pendingRoutes)) {
$this->applyRoutes();
}
if (isset($this->reverse[$name])) {
$maxMatches = 0;
$matchingParts = $this->reverse[$name][0];
// For a given route name, we want to choose the option that best matches the given parameters.
// Each routing option is an array of parts. Each part is either a constant string
// (which we don't care about here), or an array where the first element is the parameter name
// and the second element is a regex into which the parameter value is inserted, if the parameter matches.
foreach ($this->reverse[$name] as $parts) {
foreach ($parts as $i => $part) {
if (is_array($part) && Arr::exists($parameters, $part[0]) && $i > $maxMatches) {
$maxMatches = $i;
$matchingParts = $parts;
}
}
}
$fixedParts = array_map(function ($part) use ($parameters, $name) {
return $this->fixPathPart($part, $parameters, $name);
}, $matchingParts);
return '/'.ltrim(implode('', $fixedParts), '/');
}
throw new \RuntimeException("Route $name not found");
} }
} }

View File

@@ -12,9 +12,7 @@ namespace Flarum\Http;
use Closure; use Closure;
use Flarum\Frontend\Controller as FrontendController; use Flarum\Frontend\Controller as FrontendController;
use Illuminate\Contracts\Container\Container; use Illuminate\Contracts\Container\Container;
use InvalidArgumentException; use Psr\Http\Server\RequestHandlerInterface;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as Handler;
/** /**
* @internal * @internal
@@ -26,28 +24,27 @@ class RouteHandlerFactory
) { ) {
} }
public function toController(callable|string $controller): Closure public function toController(callable|string $controller): callable|string|array
{ {
return function (Request $request, array $routeParams) use ($controller) { // If it's a class and it implements the RequestHandlerInterface, we'll
$controller = $this->resolveController($controller); // assume it's a PSR-7 request handler and we'll return [controller, 'handle']
// as the callable.
if (is_string($controller) && class_exists($controller) && in_array(RequestHandlerInterface::class, class_implements($controller))) {
return [$controller, 'handle'];
}
$request = $request->withQueryParams(array_merge($request->getQueryParams(), $routeParams)); return $controller;
return $controller->handle($request);
};
} }
public function toFrontend(string $frontend, callable|string|null $content = null): Closure public function toFrontend(string $frontend, callable|string|null $content = null): callable
{ {
return $this->toController(function (Container $container) use ($frontend, $content) { $frontend = $this->container->make("flarum.frontend.$frontend");
$frontend = $container->make("flarum.frontend.$frontend");
if ($content) { if ($content) {
$frontend->content(is_callable($content) ? $content : $container->make($content)); $frontend->content(is_callable($content) ? $content : $this->container->make($content));
} }
return new FrontendController($frontend); return new FrontendController($frontend);
});
} }
public function toForum(string $content = null): Closure public function toForum(string $content = null): Closure
@@ -59,17 +56,4 @@ class RouteHandlerFactory
{ {
return $this->toFrontend('admin', $content); return $this->toFrontend('admin', $content);
} }
private function resolveController(callable|string $controller): Handler
{
$controller = is_callable($controller)
? $this->container->call($controller)
: $this->container->make($controller);
if (! $controller instanceof Handler) {
throw new InvalidArgumentException('Controller must be an instance of '.Handler::class);
}
return $controller;
}
} }

View File

@@ -0,0 +1,20 @@
<?php
namespace Flarum\Http;
use Illuminate\Routing\Router as IlluminateRouter;
class Router extends IlluminateRouter
{
public function __construct(...$args)
{
parent::__construct(...$args);
$this->routes = new RouteCollection();
}
public function forgetRoute(string $name): void
{
$this->routes->forgetNamedRoute($name);
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace Flarum\Http;
use Illuminate\Contracts\Container\Container;
use Illuminate\Routing\RoutingServiceProvider as IlluminateRoutingServiceProvider;
class RoutingServiceProvider extends IlluminateRoutingServiceProvider
{
protected function registerRouter(): void
{
$this->app->singleton('router', function (Container $container) {
return new Router($container['events'], $container);
});
}
}

View File

@@ -9,15 +9,12 @@
namespace Flarum\Http; namespace Flarum\Http;
use Flarum\Foundation\AppInterface;
use Flarum\Foundation\ErrorHandling\LogReporter; use Flarum\Foundation\ErrorHandling\LogReporter;
use Flarum\Foundation\SiteInterface; use Flarum\Foundation\SiteInterface;
use Illuminate\Contracts\Container\Container; use Illuminate\Contracts\Container\Container;
use Laminas\Diactoros\Response; use Illuminate\Http\Request;
use Laminas\Diactoros\ServerRequest; use Illuminate\Routing\Pipeline;
use Laminas\Diactoros\ServerRequestFactory;
use Laminas\HttpHandlerRunner\Emitter\SapiEmitter;
use Laminas\HttpHandlerRunner\RequestHandlerRunner;
use Laminas\Stratigility\Middleware\ErrorResponseGenerator;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Throwable; use Throwable;
@@ -30,18 +27,17 @@ class Server
public function listen(): void public function listen(): void
{ {
$runner = new RequestHandlerRunner( $request = Request::capture();
$this->safelyBootAndGetHandler(), $siteApp = $this->safelyBoot();
new SapiEmitter, $container = $siteApp->getContainer();
[ServerRequestFactory::class, 'fromGlobals'], $globalMiddleware = $siteApp->getMiddlewareStack();
function (Throwable $e) {
$generator = new ErrorResponseGenerator;
return $generator($e, new ServerRequest, new Response); (new Pipeline($container))
} ->send($request)
); ->through($globalMiddleware)
->then(function (Request $request) use ($container) {
$runner->run(); return $container->make(Router::class)->dispatch($request);
});
} }
/** /**
@@ -52,10 +48,10 @@ class Server
* *
* @throws Throwable * @throws Throwable
*/ */
private function safelyBootAndGetHandler() // @phpstan-ignore-line private function safelyBoot(): AppInterface
{ {
try { try {
return $this->site->bootApp()->getRequestHandler(); return $this->site->bootApp();
} catch (Throwable $e) { } catch (Throwable $e) {
// Apply response code first so whatever happens, it's set before anything is printed // Apply response code first so whatever happens, it's set before anything is printed
http_response_code(500); http_response_code(500);