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

chore: use ExceptionHandler contract

This commit is contained in:
Sami Mazouz
2023-08-13 17:43:14 +01:00
parent e1ab77f66a
commit 2a0f8ff7ed
14 changed files with 119 additions and 174 deletions

View File

@@ -88,6 +88,7 @@
"symfony/mime": "^6.3",
"symfony/polyfill-intl-messageformatter": "^1.27",
"symfony/postmark-mailer": "^6.3",
"symfony/psr-http-message-bridge": "^2.3",
"symfony/translation": "^6.3",
"symfony/translation-contracts": "^2.5",
"symfony/yaml": "^6.3",

View File

@@ -37,7 +37,6 @@ class AdminServiceProvider extends AbstractServiceProvider
$this->container->singleton('flarum.admin.middleware', function () {
return [
HttpMiddleware\InjectActorReference::class,
'flarum.admin.error_handler',
HttpMiddleware\StartSession::class,
HttpMiddleware\RememberFromCookie::class,
HttpMiddleware\AuthenticateWithSession::class,
@@ -50,14 +49,6 @@ class AdminServiceProvider extends AbstractServiceProvider
];
});
$this->container->bind('flarum.admin.error_handler', function (Container $container) {
return new HttpMiddleware\HandleErrors(
$container->make(Registry::class),
$container['flarum.config']->inDebugMode() ? $container->make(WhoopsFormatter::class) : $container->make(ViewFormatter::class),
$container->tagged(Reporter::class)
);
});
$this->container->bind('flarum.assets.admin', function (Container $container) {
/** @var \Flarum\Frontend\Assets $assets */
$assets = $container->make('flarum.assets.factory')('admin');

View File

@@ -45,7 +45,6 @@ class ApiServiceProvider extends AbstractServiceProvider
$this->container->singleton('flarum.api.middleware', function () {
return [
HttpMiddleware\InjectActorReference::class,
'flarum.api.error_handler',
Middleware\FakeHttpMethods::class,
HttpMiddleware\StartSession::class,
HttpMiddleware\RememberFromCookie::class,
@@ -57,14 +56,6 @@ class ApiServiceProvider extends AbstractServiceProvider
];
});
$this->container->bind('flarum.api.error_handler', function (Container $container) {
return new HttpMiddleware\HandleErrors(
$container->make(Registry::class),
new JsonApiFormatter($container['flarum.config']->inDebugMode()),
$container->tagged(Reporter::class)
);
});
$this->container->singleton('flarum.api.notification_serializers', function () {
return [
'discussionRenamed' => BasicDiscussionSerializer::class

View File

@@ -44,7 +44,6 @@ class ForumServiceProvider extends AbstractServiceProvider
$this->container->singleton('flarum.forum.middleware', function () {
return [
HttpMiddleware\InjectActorReference::class,
'flarum.forum.error_handler',
HttpMiddleware\CollectGarbage::class,
HttpMiddleware\StartSession::class,
HttpMiddleware\RememberFromCookie::class,
@@ -58,14 +57,6 @@ class ForumServiceProvider extends AbstractServiceProvider
];
});
$this->container->bind('flarum.forum.error_handler', function (Container $container) {
return new HttpMiddleware\HandleErrors(
$container->make(Registry::class),
$container['flarum.config']->inDebugMode() ? $container->make(WhoopsFormatter::class) : $container->make(ViewFormatter::class),
$container->tagged(Reporter::class)
);
});
$this->container->bind('flarum.assets.forum', function (Container $container) {
/** @var Assets $assets */
$assets = $container->make('flarum.assets.factory')('forum');

View File

@@ -83,6 +83,7 @@ class Application extends IlluminateContainer implements LaravelApplication
protected function registerBaseServiceProviders(): void
{
$this->register(new EventServiceProvider($this));
$this->register(new ErrorServiceProvider($this));
$this->register(new RoutingServiceProvider($this));
// Because we need to check very early if the version of the app

View File

@@ -42,7 +42,6 @@ class RegisterCoreProviders implements IlluminateBootstrapperInterface
$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);

View File

@@ -0,0 +1,93 @@
<?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\Foundation\ErrorHandling;
use Flarum\Foundation\Config;
use Illuminate\Contracts\Debug\ExceptionHandler as ExceptionHandling;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Symfony\Component\Console\Application as ConsoleApplication;
use Symfony\Component\HttpFoundation\Response;
use Throwable;
/**
* Catch exceptions thrown in the routing stack and console and handle them safely.
*
* All errors will be rendered using the provided formatter. In addition,
* unknown errors will be passed on to one or multiple
* {@see \Flarum\Foundation\ErrorHandling\Reporter} instances.
*/
class ExceptionHandler implements ExceptionHandling
{
protected array $handledErrors = [];
public function __construct(
protected readonly Registry $registry,
protected readonly iterable $formatters,
/** @var \Flarum\Foundation\ErrorHandling\Reporter[] $reporters */
protected readonly iterable $reporters,
protected readonly Config $config
) {
}
public function report(Throwable $e): void
{
$error = $this->getHandledError($e);
if ($error->shouldBeReported()) {
foreach ($this->reporters as $reporter) {
$reporter->report($e);
}
}
}
public function render($request, Throwable $e): Response /** @phpstan-ignore-line */
{
return $this->resolveFormatter($request)->format(
$this->getHandledError($e), $request
);
}
public function renderForConsole($output, Throwable $e): void
{
(new ConsoleApplication())->renderThrowable($e, $output);
}
public function shouldReport(Throwable $e): bool
{
return $this->getHandledError($e)->shouldBeReported();
}
/**
* Get and cache the handled error for the given exception.
*/
protected function getHandledError(Throwable $e): HandledError
{
return $this->handledErrors[$this->exceptionKey($e)] ??= $this->registry->handle($e);
}
/**
* Get a unique key for the given exception.
*/
protected function exceptionKey(Throwable $e): string
{
return get_class($e).':'.$e->getMessage().$e->getLine();
}
protected function resolveFormatter(Request $request): HttpFormatter
{
return match (true) {
$request->expectsJson(),
$request->routeIs('api.*') => Arr::first($this->formatters, fn (HttpFormatter $formatter) => $formatter instanceof JsonApiFormatter),
$this->config->inDebugMode() => Arr::first($this->formatters, fn (HttpFormatter $formatter) => $formatter instanceof WhoopsFormatter),
default => Arr::first($this->formatters, fn (HttpFormatter $formatter) => $formatter instanceof ViewFormatter),
};
}
}

View File

@@ -10,6 +10,7 @@
namespace Flarum\Foundation\ErrorHandling;
use Flarum\Api\JsonApiResponse;
use Flarum\Foundation\Config;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
use Tobscure\JsonApi\Document;
@@ -22,7 +23,7 @@ use Tobscure\JsonApi\Document;
class JsonApiFormatter implements HttpFormatter
{
public function __construct(
private readonly bool $includeTrace = false
private readonly Config $config
) {
}
@@ -46,7 +47,7 @@ class JsonApiFormatter implements HttpFormatter
'code' => $error->getType(),
];
if ($this->includeTrace) {
if ($this->config->inDebugMode()) {
$default['detail'] = (string) $error->getException();
}

View File

@@ -11,6 +11,9 @@ namespace Flarum\Foundation;
use Flarum\Extension\Exception as ExtensionException;
use Flarum\Foundation\ErrorHandling as Handling;
use Flarum\Foundation\ErrorHandling\ExceptionHandler;
use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Debug\ExceptionHandler as ExceptionHandlerContract;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Validation\ValidationException as IlluminateValidationException;
use Tobscure\JsonApi\Exception\InvalidParameterException;
@@ -19,6 +22,13 @@ class ErrorServiceProvider extends AbstractServiceProvider
{
public function register(): void
{
$this->container->singleton(ExceptionHandlerContract::class, function (Container $container) {
return $container->make(ExceptionHandler::class, [
'formatters' => $container->tagged(Handling\HttpFormatter::class),
'reporters' => $container->tagged(Handling\Reporter::class),
]);
});
$this->container->singleton('flarum.error.statuses', function () {
return [
// 400 Bad Request
@@ -73,5 +83,11 @@ class ErrorServiceProvider extends AbstractServiceProvider
});
$this->container->tag(Handling\LogReporter::class, Handling\Reporter::class);
$this->container->tag([
Handling\JsonApiFormatter::class,
Handling\ViewFormatter::class,
Handling\WhoopsFormatter::class,
], Handling\HttpFormatter::class);
}
}

View File

@@ -1,57 +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 Closure;
use Flarum\Foundation\ErrorHandling\HttpFormatter;
use Flarum\Foundation\ErrorHandling\Registry;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
use Throwable;
/**
* Catch exceptions thrown in a PSR-15 middleware stack and handle them safely.
*
* All errors will be rendered using the provided formatter. In addition,
* unknown errors will be passed on to one or multiple
* {@see \Flarum\Foundation\ErrorHandling\Reporter} instances.
*/
class HandleErrors implements IlluminateMiddlewareInterface
{
public function __construct(
protected Registry $registry,
protected HttpFormatter $formatter,
/** @var \Flarum\Foundation\ErrorHandling\Reporter[] $reporters */
protected iterable $reporters
) {
}
/**
* Catch all errors that happen during further middleware execution.
*/
public function handle(Request $request, Closure $next): Response
{
try {
return $next($request);
} catch (Throwable $e) {
$error = $this->registry->handle($e);
if ($error->shouldBeReported()) {
foreach ($this->reporters as $reporter) {
$reporter->report($error->getException());
}
}
// dump($e);
return $this->formatter->format($error, $request);
}
}
}

View File

@@ -16,13 +16,13 @@ class UrlGenerator extends IlluminateUrlGenerator
public function base(string $frontend): string
{
$url = $this->config->url();
$url = rtrim($this->config->url(), '/');
if ($frontend) {
$url .= '/'.$this->config->path($frontend);
}
return $url;
return rtrim($url, '/');
}
public function path(string $frontend, string $path): string

View File

@@ -10,23 +10,18 @@
namespace Flarum\Install;
use Flarum\Foundation\AppInterface;
use Flarum\Foundation\ErrorHandling\Registry;
use Flarum\Foundation\ErrorHandling\Reporter;
use Flarum\Foundation\ErrorHandling\WhoopsFormatter;
use Flarum\Http\Middleware as HttpMiddleware;
use Flarum\Install\Console\InstallCommand;
use Illuminate\Contracts\Container\Container;
use Laminas\Stratigility\MiddlewarePipe;
use Psr\Http\Server\RequestHandlerInterface;
use Illuminate\Contracts\Foundation\Application;
class Installer implements AppInterface
{
public function __construct(
protected Container $container
protected Application $container
) {
}
public function getContainer(): Container
public function getContainer(): Application
{
return $this->container;
}
@@ -34,11 +29,6 @@ class Installer implements AppInterface
public function getMiddlewareStack(): array
{
return [
new HttpMiddleware\HandleErrors(
$this->container->make(Registry::class),
$this->container->make(WhoopsFormatter::class),
$this->container->tagged(Reporter::class)
),
$this->container->make(HttpMiddleware\StartSession::class),
];
}

View File

@@ -1,68 +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\Queue;
use Illuminate\Contracts\Debug\ExceptionHandler as ExceptionHandling;
use Psr\Log\LoggerInterface;
use Throwable;
class ExceptionHandler implements ExceptionHandling
{
public function __construct(
private readonly LoggerInterface $logger
) {
}
/**
* Report or log an exception.
*
* @param Throwable $e
* @return void
*/
public function report(Throwable $e)
{
$this->logger->error((string) $e);
}
/**
* Render an exception into an HTTP response.
*
* @param \Illuminate\Http\Request $request
* @param Throwable $e
* @return void
*/
public function render($request, Throwable $e) /** @phpstan-ignore-line */
{
// dd($e);
}
/**
* Render an exception to the console.
*
* @param \Symfony\Component\Console\Output\OutputInterface $output
* @param Throwable $e
* @return void
*/
public function renderForConsole($output, Throwable $e)
{
// TODO: Implement renderForConsole() method.
}
/**
* Determine if the exception should be reported.
*
* @param Throwable $e
* @return bool
*/
public function shouldReport(Throwable $e)
{
return true;
}
}

View File

@@ -61,10 +61,6 @@ class QueueServiceProvider extends AbstractServiceProvider
return $queue;
});
$this->container->singleton(ExceptionHandling::class, function (Container $container) {
return new ExceptionHandler($container['log']);
});
$this->container->singleton(Worker::class, function (Container $container) {
/** @var Config $config */
$config = $container->make(Config::class);