diff --git a/composer.json b/composer.json index a3c0e4b..1d3a4d7 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "autoload": { "psr-4": { - "App\\": "src/" + "IQBall\\": "src/" } }, "require": { diff --git a/phpstan.neon b/phpstan.neon index 715fe84..346baaa 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -3,13 +3,10 @@ parameters: level: 6 paths: - src - - public scanFiles: - config.php - sql/database.php - profiles/dev-config-profile.php - profiles/prod-config-profile.php - - public/api/index.php excludePaths: - - src/react-display-file.php - - public/api/index.php + - src/App/react-display-file.php diff --git a/public/api/index.php b/public/api/index.php index cb278df..eeac0c1 100644 --- a/public/api/index.php +++ b/public/api/index.php @@ -5,84 +5,39 @@ require "../../vendor/autoload.php"; require "../../sql/database.php"; require "../utils.php"; -use App\Connexion; -use App\Controller\Api\APIAuthController; -use App\Controller\Api\APITacticController; -use App\Data\Account; -use App\Gateway\AccountGateway; -use App\Gateway\TacticInfoGateway; -use App\Http\HttpResponse; -use App\Http\JsonHttpResponse; -use App\Http\ViewHttpResponse; -use App\Model\AuthModel; -use App\Model\TacticModel; -use App\Session\PhpSessionHandle; -use App\Validation\ValidationFail; +use IQBall\Api\ApiAction; +use IQBall\Core\Connection; +use IQBall\Api\APIAuthController; +use IQBall\Api\APITacticController; +use IQBall\Core\Data\Account; +use IQBall\Core\Gateway\AccountGateway; +use IQBall\Core\Gateway\TacticInfoGateway; +use IQBall\Core\Http\HttpResponse; +use IQBall\Core\Http\JsonHttpResponse; +use IQBall\Core\Http\ViewHttpResponse; +use IQBall\Core\Model\AuthModel; +use IQBall\Core\Model\TacticModel; +use IQBall\Core\Session\PhpSessionHandle; +use IQBall\Core\Validation\ValidationFail; function getTacticController(): APITacticController { - return new APITacticController(new TacticModel(new TacticInfoGateway(new Connexion(get_database())))); + return new APITacticController(new TacticModel(new TacticInfoGateway(new Connection(get_database())))); } function getAuthController(): APIAuthController { - return new APIAuthController(new AuthModel(new AccountGateway(new Connexion(get_database())))); + return new APIAuthController(new AuthModel(new AccountGateway(new Connection(get_database())))); } -/** - * A Front controller action - */ -//TODO workaround for generic Action -class ApiAction { - /** - * @var callable(mixed[]): HttpResponse $action action to call - */ - private $action; - - private bool $isAuthRequired; - - /** - * @param callable(mixed[]): HttpResponse $action - */ - private function __construct(callable $action, bool $isAuthRequired) { - $this->action = $action; - $this->isAuthRequired = $isAuthRequired; - } +function getRoutes(): AltoRouter { + $router = new AltoRouter(); + $router->setBasePath(get_public_path() . "/api"); - public function isAuthRequired(): bool { - return $this->isAuthRequired; - } + $router->map("POST", "/tactic/[i:id]/edit/name", ApiAction::auth(fn(int $id, Account $acc) => getTacticController()->updateName($id, $acc))); + $router->map("GET", "/tactic/[i:id]", ApiAction::auth(fn(int $id, Account $acc) => getTacticController()->getTacticInfo($id, $acc))); + $router->map("POST", "/tactic/new", ApiAction::auth(fn(Account $acc) => getTacticController()->newTactic($acc))); + $router->map("POST", "/auth", ApiAction::noAuth(fn() => getAuthController()->authorize())); - /** - * @param mixed[] $params - * @param ?Account $account - * @return HttpResponse - */ - public function run(array $params, ?Account $account): HttpResponse { - $params = array_values($params); - if ($this->isAuthRequired) { - if ($account == null) { - throw new Exception("action requires authorization."); - } - $params[] = $account; - } - - return call_user_func_array($this->action, $params); - } - - /** - * @param callable(mixed[]): HttpResponse $action - * @return ApiAction an action that does not require to have an authorization. - */ - public static function noAuth(callable $action): ApiAction { - return new ApiAction($action, false); - } - - /** - * @param callable(mixed[]): HttpResponse $action - * @return ApiAction an action that does require to have an authorization. - */ - public static function auth(callable $action): ApiAction { - return new ApiAction($action, true); - } + return $router; } /** @@ -97,7 +52,7 @@ function handleMatch(array $match): HttpResponse { $action = $match['target']; if (!$action instanceof ApiAction) { - throw new Exception("routed action is not an Action object."); + throw new Exception("routed action is not an AppAction object."); } $auth = null; @@ -122,27 +77,23 @@ function tryGetAuthorization(): ?Account { } $token = $headers['Authorization']; - $gateway = new AccountGateway(new Connexion(get_database())); + $gateway = new AccountGateway(new Connection(get_database())); return $gateway->getAccountFromToken($token); } -$router = new AltoRouter(); -$router->setBasePath(get_public_path() . "/api"); - -$router->map("POST", "/tactic/[i:id]/edit/name", ApiAction::auth(fn(int $id, Account $acc) => getTacticController()->updateName($id, $acc))); -$router->map("GET", "/tactic/[i:id]", ApiAction::auth(fn(int $id, Account $acc) => getTacticController()->getTacticInfo($id, $acc))); -$router->map("POST", "/tactic/new", ApiAction::auth(fn(Account $acc) => getTacticController()->newTactic($acc))); -$router->map("POST", "/auth", ApiAction::noAuth(fn() => getAuthController()->authorize())); - -$match = $router->match(); +function render(HttpResponse $response): void { + http_response_code($response->getCode()); + foreach ($response->getHeaders() as $header => $value) { + header("$header: $value"); + } -$response = handleMatch($match); -http_response_code($response->getCode()); - -if ($response instanceof JsonHttpResponse) { - header('Content-type: application/json'); - echo $response->getJson(); -} elseif ($response instanceof ViewHttpResponse) { - throw new Exception("API returned a view http response."); + if ($response instanceof JsonHttpResponse) { + header('Content-type: application/json'); + echo $response->getJson(); + } elseif ($response instanceof ViewHttpResponse) { + throw new Exception("API returned a view http response."); + } } + +render(handleMatch(getRoutes()->match())); diff --git a/public/index.php b/public/index.php index 8d1f148..07d58c1 100644 --- a/public/index.php +++ b/public/index.php @@ -4,96 +4,157 @@ require "../vendor/autoload.php"; require "../config.php"; require "../sql/database.php"; -require "utils.php"; -require "../src/react-display.php"; - -use App\Controller\AuthController; -use App\Controller\EditorController; -use App\Controller\Route\Action; -use App\Controller\Route\FrontController; -use App\Controller\TeamController; -use App\Controller\UserController; -use App\Controller\VisualizerController; -use App\Gateway\AccountGateway; -use App\Gateway\TacticInfoGateway; -use App\Gateway\TeamGateway; -use App\Model\AuthModel; -use App\Model\TacticModel; -use App\Model\TeamModel; -use App\Session\MutableSessionHandle; -use App\Session\PhpSessionHandle; -use App\Connexion; -use App\Session\SessionHandle; - -$connexion = new Connexion(get_database()); +require "../src/utils.php"; +require "../src/App/react-display.php"; + +use IQBall\App\AppAction; +use IQBall\App\Controller\AuthController; +use IQBall\App\Controller\EditorController; +use IQBall\App\Controller\TeamController; +use IQBall\App\Controller\UserController; +use IQBall\App\Controller\VisualizerController; +use IQBall\Core\Connection; +use IQBall\Core\Gateway\AccountGateway; +use IQBall\Core\Gateway\TacticInfoGateway; +use IQBall\Core\Gateway\TeamGateway; +use IQBall\Core\Http\HttpCodes; +use IQBall\Core\Http\HttpResponse; +use IQBall\Core\Http\JsonHttpResponse; +use IQBall\Core\Http\ViewHttpResponse; +use IQBall\Core\Model\AuthModel; +use IQBall\Core\Model\TacticModel; +use IQBall\Core\Model\TeamModel; +use IQBall\Core\Session\MutableSessionHandle; +use IQBall\Core\Session\PhpSessionHandle; +use IQBall\Core\Session\SessionHandle; +use IQBall\Core\Validation\ValidationFail; +use Twig\Environment; +use Twig\Error\LoaderError; +use Twig\Error\RuntimeError; +use Twig\Error\SyntaxError; +use Twig\Loader\FilesystemLoader; + +function getConnection(): Connection { + return new Connection(get_database()); +} function getUserController(): UserController { - global $connexion; - return new UserController(new TacticModel(new TacticInfoGateway($connexion))); + return new UserController(new TacticModel(new TacticInfoGateway(getConnection()))); } function getVisualizerController(): VisualizerController { - global $connexion; - return new VisualizerController(new TacticModel(new TacticInfoGateway($connexion))); + return new VisualizerController(new TacticModel(new TacticInfoGateway(getConnection()))); } function getEditorController(): EditorController { - global $connexion; - return new EditorController(new TacticModel(new TacticInfoGateway($connexion))); + return new EditorController(new TacticModel(new TacticInfoGateway(getConnection()))); } function getTeamController(): TeamController { - global $connexion; - return new TeamController(new TeamModel(new TeamGateway($connexion))); + return new TeamController(new TeamModel(new TeamGateway(getConnection()))); } function getAuthController(): AuthController { - global $connexion; - return new AuthController(new AuthModel(new AccountGateway($connexion))); + return new AuthController(new AuthModel(new AccountGateway(getConnection()))); } -function initFrontController(FrontController $fc) { +function getRoutes(): AltoRouter { + global $basePath; + + $ar = new AltoRouter(); + $ar->setBasePath($basePath); + //authentication - $fc->addRoute("GET", "/login", Action::noAuth(fn() => getAuthController()->displayLogin())); - $fc->addRoute("GET", "/register", Action::noAuth(fn() => getAuthController()->displayRegister())); - $fc->addRoute("POST", "/login", Action::noAuth(fn(SessionHandle $s) => getAuthController()->confirmLogin($_POST, $s))); - $fc->addRoute("POST", "/register", Action::noAuth(fn(SessionHandle $s) => getAuthController()->confirmRegister($_POST, $s))); + $ar->map("GET", "/login", AppAction::noAuth(fn() => getAuthController()->displayLogin())); + $ar->map("GET", "/register", AppAction::noAuth(fn() => getAuthController()->displayRegister())); + $ar->map("POST", "/login", AppAction::noAuth(fn(SessionHandle $s) => getAuthController()->confirmLogin($_POST, $s))); + $ar->map("POST", "/register", AppAction::noAuth(fn(SessionHandle $s) => getAuthController()->confirmRegister($_POST, $s))); //user-related - $fc->addRoute("GET", "/home", Action::auth(fn(SessionHandle $s) => getUserController()->home($s))); - $fc->addRoute("GET", "/settings", Action::auth(fn(SessionHandle $s) => getUserController()->settings($s))); + $ar->map("GET", "/home", AppAction::auth(fn(SessionHandle $s) => getUserController()->home($s))); + $ar->map("GET", "/settings", AppAction::auth(fn(SessionHandle $s) => getUserController()->settings($s))); //tactic-related - $fc->addRoute("GET", "/tactic/[i:id]/view", Action::auth(fn(int $id, SessionHandle $s) => getVisualizerController()->visualize($id, $s))); - $fc->addRoute("GET", "/tactic/[i:id]/edit", Action::auth(fn(int $id, SessionHandle $s) => getEditorController()->edit($id, $s))); - $fc->addRoute("GET", "/tactic/new", Action::auth(fn(SessionHandle $s) => getEditorController()->createNew($s))); + $ar->map("GET", "/tactic/[i:id]/view", AppAction::auth(fn(int $id, SessionHandle $s) => getVisualizerController()->visualize($id, $s))); + $ar->map("GET", "/tactic/[i:id]/edit", AppAction::auth(fn(int $id, SessionHandle $s) => getEditorController()->edit($id, $s))); + $ar->map("GET", "/tactic/new", AppAction::auth(fn(SessionHandle $s) => getEditorController()->createNew($s))); //team-related - $fc->addRoute("GET", "/team/new", Action::auth(fn(SessionHandle $s) => getTeamController()->displayCreateTeam($s))); - $fc->addRoute("POST", "/team/new", Action::auth(fn(SessionHandle $s) => getTeamController()->submitTeam($_POST, $s))); - $fc->addRoute("GET", "/team/search", Action::auth(fn(SessionHandle $s) => getTeamController()->displayListTeamByName($s))); - $fc->addRoute("POST", "/team/search", Action::auth(fn(SessionHandle $s) => getTeamController()->listTeamByName($_POST, $s))); - $fc->addRoute("GET", "/team/[i:id]", Action::auth(fn(int $id, SessionHandle $s) => getTeamController()->displayTeam($id, $s))); - $fc->addRoute("GET", "/team/members/add", Action::auth(fn(SessionHandle $s) => getTeamController()->displayAddMember($s))); - $fc->addRoute("POST", "/team/members/add", Action::auth(fn(SessionHandle $s) => getTeamController()->addMember($_POST, $s))); - $fc->addRoute("GET", "/team/members/remove", Action::auth(fn(SessionHandle $s) => getTeamController()->displayDeleteMember($s))); - $fc->addRoute("POST", "/team/members/remove", Action::auth(fn(SessionHandle $s) => getTeamController()->deleteMember($_POST, $s))); + $ar->map("GET", "/team/new", AppAction::auth(fn(SessionHandle $s) => getTeamController()->displayCreateTeam($s))); + $ar->map("POST", "/team/new", AppAction::auth(fn(SessionHandle $s) => getTeamController()->submitTeam($_POST, $s))); + $ar->map("GET", "/team/search", AppAction::auth(fn(SessionHandle $s) => getTeamController()->displayListTeamByName($s))); + $ar->map("POST", "/team/search", AppAction::auth(fn(SessionHandle $s) => getTeamController()->listTeamByName($_POST, $s))); + $ar->map("GET", "/team/[i:id]", AppAction::auth(fn(int $id, SessionHandle $s) => getTeamController()->displayTeam($id, $s))); + $ar->map("GET", "/team/members/add", AppAction::auth(fn(SessionHandle $s) => getTeamController()->displayAddMember($s))); + $ar->map("POST", "/team/members/add", AppAction::auth(fn(SessionHandle $s) => getTeamController()->addMember($_POST, $s))); + $ar->map("GET", "/team/members/remove", AppAction::auth(fn(SessionHandle $s) => getTeamController()->displayDeleteMember($s))); + $ar->map("POST", "/team/members/remove", AppAction::auth(fn(SessionHandle $s) => getTeamController()->deleteMember($_POST, $s))); + + return $ar; } -//this is a global variable -$basePath = get_public_path(); +function render(HttpResponse $response): void { + http_response_code($response->getCode()); + foreach ($response->getHeaders() as $header => $value) { + header("$header: $value"); + } -function run() { - global $basePath; + if ($response instanceof ViewHttpResponse) { + renderView($response); + } elseif ($response instanceof JsonHttpResponse) { + header('Content-type: application/json'); + echo $response->getJson(); + } +} - $fc = new FrontController($basePath); +function renderView(ViewHttpResponse $response): void { + $file = $response->getFile(); + $args = $response->getArguments(); + + switch ($response->getViewKind()) { + case ViewHttpResponse::REACT_VIEW: + send_react_front($file, $args); + break; + case ViewHttpResponse::TWIG_VIEW: + try { + $loader = new FilesystemLoader('../src/Views/'); + $twig = new Environment($loader); + $twig->display($file, $args); + } catch (RuntimeError | SyntaxError | LoaderError $e) { + http_response_code(500); + echo "There was an error rendering your view, please refer to an administrator.\nlogs date: " . date("YYYD, d M Y H:i:s"); + throw $e; + } + break; + } +} - initFrontController($fc); - $fc->run(PhpSessionHandle::init()); +function runAction(AppAction $action, array $params, MutableSessionHandle $session): HttpResponse { + global $basePath; + if ($action->isAuthRequired()) { + $account = $session->getAccount(); + if ($account == null) { + // put in the session the initial url the user wanted to get + $session->setInitialTarget($_SERVER['REQUEST_URI']); + return HttpResponse::redirect($basePath . "/login"); + } + } + + return $action->run($params, $session); } +function runMatch(array $match, MutableSessionHandle $session): HttpResponse { + if (!$match) { + return ViewHttpResponse::twig("error.html.twig", [ + 'failures' => [ValidationFail::notFound("Could not find page ${_SERVER['REQUEST_URI']}.")], + ], HttpCodes::NOT_FOUND); + } -run(); + return runAction($match['target'], $match['params'], $session); +} +//this is a global variable +$basePath = get_public_path(); +render(runMatch(getRoutes()->match(), PhpSessionHandle::init())); diff --git a/src/Controller/Api/APIAuthController.php b/src/Api/APIAuthController.php similarity index 77% rename from src/Controller/Api/APIAuthController.php rename to src/Api/APIAuthController.php index d9e4c23..c4f5976 100644 --- a/src/Controller/Api/APIAuthController.php +++ b/src/Api/APIAuthController.php @@ -1,13 +1,13 @@ + */ +class ApiAction extends AbstractAction { + + /** + * @param mixed[] $params + * @param ?Account $session + * @return HttpResponse + */ + public function run(array $params, $session): HttpResponse { + $params = array_values($params); + if ($this->isAuthRequired()) { + if ($session == null) { + throw new \Exception("action requires authorization."); + } + $params[] = $session; + } + + return call_user_func_array($this->action, $params); + } + + /** + * @param callable(mixed[]): HttpResponse $action + * @return ApiAction an action that does not require to have an authorization. + */ + public static function noAuth(callable $action): ApiAction { + return new ApiAction($action, false); + } + + /** + * @param callable(mixed[]): HttpResponse $action + * @return ApiAction an action that does require to have an authorization. + */ + public static function auth(callable $action): ApiAction { + return new ApiAction($action, true); + } +} \ No newline at end of file diff --git a/src/App/AppAction.php b/src/App/AppAction.php new file mode 100644 index 0000000..0e4dcdf --- /dev/null +++ b/src/App/AppAction.php @@ -0,0 +1,55 @@ + + */ +class AppAction extends AbstractAction { + + /** + * @param mixed[] $params + * @param MutableSessionHandle $session + * @return HttpResponse + * @throws Exception
+ * thrown if this action is required to be authenticated, but the given session does not contain a logged-in account. + *
+ *+ * Caller is supposed to ensure that the user is logged-in before, if `$this->isAuthRequired()` is true before + * running this action. + *
+ */ + public function run(array $params, $session): HttpResponse { + $params = array_values($params); + if ($this->isAuthRequired()) { + if ($session->getAccount() == null) { + throw new Exception("action requires authorization."); + } + } + + $params[] = $session; + return call_user_func_array($this->action, $params); + } + + /** + * @param callable(mixed[]): HttpResponse $action + * @return AppAction an action that does not require to have an authorization. + */ + public static function noAuth(callable $action): AppAction { + return new AppAction($action, false); + } + + /** + * @param callable(mixed[]): HttpResponse $action + * @return AppAction an action that does require to have an authorization. + */ + public static function auth(callable $action): AppAction { + return new AppAction($action, true); + } +} diff --git a/src/Controller/AuthController.php b/src/App/Controller/AuthController.php similarity index 86% rename from src/Controller/AuthController.php rename to src/App/Controller/AuthController.php index 0ee9a46..f74e45d 100644 --- a/src/Controller/AuthController.php +++ b/src/App/Controller/AuthController.php @@ -1,14 +1,14 @@ $fails]); } @@ -44,7 +44,7 @@ class AuthController { "username" => [Validators::name(), Validators::lenBetween(2, 32)], "password" => [Validators::lenBetween(6, 256)], "confirmpassword" => [Validators::lenBetween(6, 256)], - "email" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/","invalide"),Validators::lenBetween(5, 256)], + "email" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/", "invalide"),Validators::lenBetween(5, 256)], ]); if (!empty($fails)) { return $this->displayBadFields("display_register.html.twig", $fails); @@ -73,7 +73,7 @@ class AuthController { $fails = []; $request = HttpRequest::from($request, $fails, [ "password" => [Validators::lenBetween(6, 256)], - "email" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/","invalide"),Validators::lenBetween(5, 256)], + "email" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/", "invalide"),Validators::lenBetween(5, 256)], ]); if (!empty($fails)) { return $this->displayBadFields("display_login.html.twig", $fails); diff --git a/src/Controller/EditorController.php b/src/App/Controller/EditorController.php similarity index 80% rename from src/Controller/EditorController.php rename to src/App/Controller/EditorController.php index bfe80b0..0b47cba 100644 --- a/src/Controller/EditorController.php +++ b/src/App/Controller/EditorController.php @@ -1,14 +1,14 @@ $request + * @param SessionHandle $session + * @return HttpResponse + */ public function submitTeam(array $request, SessionHandle $session): HttpResponse { - - $errors = []; - - $request = HttpRequest::from($request, $errors, [ + $failures = []; + $request = HttpRequest::from($request, $failures, [ "name" => [Validators::lenBetween(1, 32), Validators::nameWithSpaces()], "mainColor" => [Validators::regex('/#(?:[0-9a-fA-F]{6})/')], "secondColor" => [Validators::regex('/#(?:[0-9a-fA-F]{6})/')], "picture" => [Validators::isURL()], ]); - if (!empty($errors)) { + if (!empty($failures)) { $badFields = []; - foreach ($errors as $e) { + foreach ($failures as $e) { if ($e instanceof FieldValidationFail) { $badFields[] = $e->getFieldName(); } @@ -88,23 +91,33 @@ class TeamController { return ViewHttpResponse::twig('display_team.html.twig', ['team' => $result]); } + /** + * @param arrayMon profil
diff --git a/src/Views/insert_team.html.twig b/src/App/Views/insert_team.html.twig similarity index 100% rename from src/Views/insert_team.html.twig rename to src/App/Views/insert_team.html.twig diff --git a/src/Views/list_team_by_name.html.twig b/src/App/Views/list_team_by_name.html.twig similarity index 100% rename from src/Views/list_team_by_name.html.twig rename to src/App/Views/list_team_by_name.html.twig diff --git a/src/react-display-file.php b/src/App/react-display-file.php similarity index 100% rename from src/react-display-file.php rename to src/App/react-display-file.php diff --git a/src/react-display.php b/src/App/react-display.php similarity index 100% rename from src/react-display.php rename to src/App/react-display.php diff --git a/src/Controller/Route/Action.php b/src/Controller/Route/Action.php deleted file mode 100644 index 61fd13a..0000000 --- a/src/Controller/Route/Action.php +++ /dev/null @@ -1,71 +0,0 @@ -action = $action; - $this->isAuthRequired = $isAuthRequired; - } - - public function isAuthRequired(): bool { - return $this->isAuthRequired; - } - - /** - * @param mixed[] $params - * @param SessionHandle $session - * @return HttpResponse - * @throws Exception
- * thrown if this action is required to be authenticated, but the given session does not contain a logged-in account. - *
- *- * Caller is supposed to ensure that the user is logged-in before, if `$this->isAuthRequired()` is true before - * running this action. - *
- */ - public function run(array $params, SessionHandle $session): HttpResponse { - $params = array_values($params); - if ($this->isAuthRequired) { - if ($session->getAccount() == null) { - throw new Exception("action requires authorization."); - } - } - - $params[] = $session; - return call_user_func_array($this->action, $params); - } - - /** - * @param callable(mixed[]): HttpResponse $action - * @return Action an action that does not require to have an authorization. - */ - public static function noAuth(callable $action): Action { - return new Action($action, false); - } - - /** - * @param callable(mixed[]): HttpResponse $action - * @return Action an action that does require to have an authorization. - */ - public static function auth(callable $action): Action { - return new Action($action, true); - } -} \ No newline at end of file diff --git a/src/Controller/Route/FrontController.php b/src/Controller/Route/FrontController.php deleted file mode 100644 index 05b440e..0000000 --- a/src/Controller/Route/FrontController.php +++ /dev/null @@ -1,143 +0,0 @@ -router = $this->createRouter($basePath); - $this->basePath = $basePath; - } - - public function addRoute(string $method, string $path, Action $action): void { - $this->router->map($method, $path, $action); - } - - /** - * @param MutableSessionHandle $session - * @return void - * @throws LoaderError - * @throws RuntimeError - * @throws SyntaxError - */ - public function run(MutableSessionHandle $session): void { - $match = $this->router->match(); - if ($match) { - $this->handleMatch($match, $session); - return; - } - - $this->displayViewByKind(ViewHttpResponse::twig("error.html.twig", [ - 'failures' => [ValidationFail::notFound("Could not find page ${_SERVER['REQUEST_URI']}.")], - ], HttpCodes::NOT_FOUND)); - } - - /** - * Create a new instance of an AltoRouter - * - * @param string $basePath - * @return AltoRouter - */ - public function createRouter(string $basePath): AltoRouter { - $router = new AltoRouter(); - $router->setBasePath($basePath); - return $router; - } - - /** - * @param array