Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions formwork/config/system.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,6 @@ debug:
editorUri: 'editor://file/{{filename}}:{{line}}'
contextLines: 5

errors:
setHandlers: true

fields:
path: '${%SYSTEM_PATH%}/fields'
dynamic:
Expand Down
10 changes: 5 additions & 5 deletions formwork/routes.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?php

use Formwork\Config\Config;
use Formwork\ErrorHandlers;
use Formwork\Controllers\ErrorsControllerInterface;
use Formwork\Http\RedirectResponse;
use Formwork\Http\Request;
use Formwork\Http\ResponseStatus;
Expand Down Expand Up @@ -47,7 +47,7 @@

'filters' => [
'request.validateSize' => [
'action' => static function (Config $config, Request $request, Router $router, ErrorHandlers $errorHandlers) {
'action' => static function (Config $config, Request $request, Router $router, ErrorsControllerInterface $errorsController) {
if ($config->get('system.panel.enabled') && $router->requestHasPrefix($config->get('system.panel.root'))) {
return;
}
Expand All @@ -57,7 +57,7 @@
$maxSize = FileSystem::shorthandToBytes(ini_get('post_max_size') ?: '0');

if ($request->contentLength() > $maxSize && $maxSize > 0) {
$errorHandlers->displayErrorPage(ResponseStatus::PayloadTooLarge);
return $errorsController->error(ResponseStatus::PayloadTooLarge);
}
}
},
Expand All @@ -66,7 +66,7 @@
],

'request.validateCsrf' => [
'action' => static function (Config $config, Request $request, Router $router, CsrfToken $csrfToken, ErrorHandlers $errorHandlers) {
'action' => static function (Config $config, Request $request, Router $router, CsrfToken $csrfToken, ErrorsControllerInterface $errorsController) {
if ($config->get('system.panel.enabled') && $router->requestHasPrefix($config->get('system.panel.root'))) {
return;
}
Expand All @@ -76,7 +76,7 @@

if (!($csrfToken->validate($tokenName, $token))) {
$csrfToken->destroy($tokenName);
$errorHandlers->displayErrorPage(ResponseStatus::Forbidden);
return $errorsController->error(ResponseStatus::Forbidden);
}
},
'methods' => ['POST'],
Expand Down
42 changes: 31 additions & 11 deletions formwork/src/App.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
namespace Formwork;

use BadMethodCallException;
use ErrorException;
use Formwork\Cache\AbstractCache;
use Formwork\Cache\FilesCache;
use Formwork\Config\Config;
use Formwork\Controllers\ErrorsController;
use Formwork\Controllers\ErrorsControllerInterface;
use Formwork\Fields\Dynamic\DynamicFieldValue;
use Formwork\Files\FileFactory;
use Formwork\Files\FileUriGenerator;
Expand All @@ -20,7 +23,6 @@
use Formwork\Security\CsrfToken;
use Formwork\Services\Container;
use Formwork\Services\Loaders\ConfigServiceLoader;
use Formwork\Services\Loaders\ErrorHandlersServiceLoader;
use Formwork\Services\Loaders\LanguagesServiceLoader;
use Formwork\Services\Loaders\PanelServiceLoader;
use Formwork\Services\Loaders\SchemesServiceLoader;
Expand All @@ -37,6 +39,7 @@
use Formwork\Users\Users;
use Formwork\Utils\Str;
use Formwork\View\ViewFactory;
use Throwable;

final class App
{
Expand All @@ -57,8 +60,6 @@ public function __construct()
$this->initializeSingleton();

$this->container = new Container();

$this->loadServices($this->container);
}

/**
Expand Down Expand Up @@ -134,15 +135,22 @@ public function getService(string $name): object
*/
public function run(): Response
{
$this->loadRoutes();
$this->loadErrorHandler();

try {
$this->loadServices($this->container);

DynamicFieldValue::$vars = $this->container->call(require $this->config()->get('system.fields.dynamic.vars.file'));
$this->loadRoutes();

$request = $this->request();
DynamicFieldValue::$vars = $this->container->call(require $this->config()->get('system.fields.dynamic.vars.file'));

$response = $this->router()->dispatch();
$response = $this->router()->dispatch();
} catch (Throwable $throwable) {
$controller = $this->container->get(ErrorsControllerInterface::class);
$response = $controller->error(throwable: $throwable);
}

$response->prepare($request)->send();
$response->prepare($this->request())->send();

return $response;
}
Expand All @@ -163,9 +171,9 @@ protected function loadServices(Container $container): void
$container->define(Request::class, fn () => Request::fromGlobals())
->alias('request');

$container->define(ErrorHandlers::class)
->loader(ErrorHandlersServiceLoader::class)
->lazy(!$this->config()->get('system.errors.setHandlers', true));
$container->define(ErrorsController::class)
->alias(ErrorsControllerInterface::class)
->lazy(false);

$container->define(CsrfToken::class)
->alias('csrfToken');
Expand Down Expand Up @@ -243,4 +251,16 @@ protected function loadRoutes(): void

$this->router()->loadFromFile($this->config()->get('system.routes.files.system'));
}

protected function loadErrorHandler(): void
{
ini_set('display_errors', 0);

set_error_handler(function (int $severity, string $message, string $file, int $line): bool {
if (!(error_reporting() & $severity) || $severity === E_USER_DEPRECATED) {
return false;
}
throw new ErrorException($message, 0, $severity, $file, $line);
});
}
}
41 changes: 39 additions & 2 deletions formwork/src/Controllers/AbstractController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@

namespace Formwork\Controllers;

use Formwork\App;
use Formwork\Config\Config;
use Formwork\Http\Request;
use Formwork\Http\Response;
use Formwork\Services\Container;
use Formwork\Utils\Str;
use Formwork\View\ViewFactory;
use InvalidArgumentException;

abstract class AbstractController
{
Expand All @@ -11,8 +18,38 @@ abstract class AbstractController
*/
protected string $name;

public function __construct()
{
public function __construct(
private readonly Container $container,
protected App $app,
protected Config $config,
protected ViewFactory $viewFactory,
protected Request $request,
) {
$this->name = strtolower(Str::beforeLast(Str::afterLast(static::class, '\\'), 'Controller'));
}

/**
* Render a view
*
* @param array<string, mixed> $data
*/
protected function view(string $name, array $data = []): string
{
return $this->viewFactory->make($name, $data)->render();
}

/**
* Forward the request to another controller
*
* @param class-string<self> $controller
* @param array<string, mixed> $parameters
*/
protected function forward(string $controller, string $action, array $parameters = []): Response
{
if (!is_subclass_of($controller, AbstractController::class)) {
throw new InvalidArgumentException(sprintf('Controllers must extend %s', AbstractController::class));
}
$instance = $this->container->build($controller);
return $this->container->call($instance->$action(...), $parameters);
}
}
7 changes: 3 additions & 4 deletions formwork/src/Controllers/AssetController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,16 @@

namespace Formwork\Controllers;

use Formwork\Config\Config;
use Formwork\Http\FileResponse;
use Formwork\Router\RouteParams;
use Formwork\Utils\Exceptions\FileNotFoundException;
use Formwork\Utils\FileSystem;

class AssetController
class AssetController extends AbstractController
{
public function load(RouteParams $routeParams, Config $config): FileResponse
public function load(RouteParams $routeParams): FileResponse
{
$path = FileSystem::joinPaths($config->get('system.images.processPath'), $routeParams->get('id'), $routeParams->get('name'));
$path = FileSystem::joinPaths($this->config->get('system.images.processPath'), $routeParams->get('id'), $routeParams->get('name'));

if (FileSystem::isFile($path)) {
return new FileResponse($path);
Expand Down
55 changes: 55 additions & 0 deletions formwork/src/Controllers/ErrorsController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

namespace Formwork\Controllers;

use Formwork\Http\JsonResponse;
use Formwork\Http\Response;
use Formwork\Http\ResponseStatus;
use Throwable;

class ErrorsController extends AbstractController implements ErrorsControllerInterface
{
public function error(ResponseStatus $responseStatus = ResponseStatus::InternalServerError, ?Throwable $throwable = null): Response
{
Response::cleanOutputBuffers();

$response = $this->request->isXmlHttpRequest()
? JsonResponse::error('Error', $responseStatus)
: new Response($this->view(
'errors.error',
[
'status' => $responseStatus->code(),
'message' => $responseStatus->message(),
'throwable' => $throwable,
]
), $responseStatus);

if ($throwable !== null) {
error_log(sprintf(
"Uncaught %s: %s in %s:%s\nStack trace:\n%s\n",
$throwable::class,
$throwable->getMessage(),
$throwable->getFile(),
$throwable->getLine(),
$throwable->getTraceAsString()
));
}

return $response;
}

public function notFound(): Response
{
return $this->error(ResponseStatus::NotFound);
}

public function internalServerError(Throwable $throwable): Response
{
return $this->error(ResponseStatus::InternalServerError, $throwable);
}

public function forbidden(): Response
{
return $this->error(ResponseStatus::Forbidden);
}
}
12 changes: 12 additions & 0 deletions formwork/src/Controllers/ErrorsControllerInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace Formwork\Controllers;

use Formwork\Http\Response;
use Formwork\Http\ResponseStatus;
use Throwable;

interface ErrorsControllerInterface
{
public function error(ResponseStatus $responseStatus = ResponseStatus::InternalServerError, ?Throwable $throwable = null): Response;
}
19 changes: 10 additions & 9 deletions formwork/src/Controllers/PageController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,38 @@

namespace Formwork\Controllers;

use Formwork\App;
use Formwork\Cache\FilesCache;
use Formwork\Config\Config;
use Formwork\Http\FileResponse;
use Formwork\Http\RedirectResponse;
use Formwork\Http\Response;
use Formwork\Http\ResponseStatus;
use Formwork\Pages\Page;
use Formwork\Router\RouteParams;
use Formwork\Router\Router;
use Formwork\Services\Container;
use Formwork\Site;
use Formwork\Statistics\Statistics;
use Formwork\Utils\FileSystem;
use Formwork\View\ViewFactory;

class PageController extends AbstractController
{
public function __construct(protected App $app, protected Config $config, protected Router $router, protected Site $site, protected FilesCache $filesCache)
{
parent::__construct();
public function __construct(
private readonly Container $container,
protected Router $router,
protected Site $site,
protected FilesCache $filesCache
) {
$this->container->call(parent::__construct(...));
}

public function load(RouteParams $routeParams, ViewFactory $viewFactory, Statistics $statistics): Response
public function load(RouteParams $routeParams, Statistics $statistics): Response
{
if ($this->site->get('maintenance.enabled') && !$this->app->panel()?->isLoggedIn()) {
if ($this->site->get('maintenance.page') !== null) {
$route = $this->site->get('maintenance.page')->route();
} else {
$status = ResponseStatus::ServiceUnavailable;
$view = $viewFactory->make('errors.maintenance', ['status' => $status->code(), 'message' => $status->message()]);
return new Response($view->render(), $status);
return new Response($this->view('errors.maintenance', ['status' => $status->code(), 'message' => $status->message()]), $status);
}
}

Expand Down
Loading