1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
|
<?php
namespace App;
use App\Errors\ErrorResponse;
use App\Errors\Exception;
use App\Singleton;
use App\Support\Logger;
use Matrix\Enums\ErrorCode;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Exception\MethodNotAllowedException;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Symfony\Component\Routing\Loader\AttributeClassLoader;
use Symfony\Component\Routing\Loader\AttributeDirectoryLoader;
use Symfony\Component\Routing\Matcher\UrlMatcher;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
class Router
{
use Singleton;
private RouteCollection $routes;
public function __construct()
{
// load routes
$loader = new AttributeDirectoryLoader(new FileLocator(), new class() extends AttributeClassLoader {
protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, object $attr) {
$route->setDefault("_controller", [$class->getName(), $method->getName()]);
}
});
$this->routes = $loader->load(__DIR__ . "/Controllers");
}
/**
* match the current url against the routes.
* also add CORS headers.
*/
public function run(): Response
{
$request = Request::createFromGlobals();
$response = new Response();
$corsHeaders = [
"Access-Control-Allow-Origin" => "*",
"Access-Control-Allow-Methods" => "GET, POST, PUT, DELETE, OPTIONS, PATCH, HEAD",
"Access-Control-Allow-Headers" => "X-Requested-With, Content-Type, Authorization",
];
// handle OPTIONS
if ($request->isMethod("OPTIONS")) {
$response->headers->add($corsHeaders);
return $response;
}
$context = new RequestContext();
$context->fromRequest($request);
try {
$matcher = new UrlMatcher($this->routes, $context);
$match = $matcher->match(rtrim($request->getPathInfo(), "/"));
$class = $match["_controller"][0];
$method = $match["_controller"][1];
$request->attributes->add(array_diff_key(
$match,
array_flip(["_controller", "_route"])
));
Logger::logRequestToFile($request);
$response = (new $class)->$method($request);
} catch (Exception $exception) {
$response = ErrorResponse::fromException($exception);
} catch (ResourceNotFoundException $exception) {
$response = new ErrorResponse(ErrorCode::NOT_FOUND, "404", Response::HTTP_NOT_FOUND);
} catch (MethodNotAllowedException $exception) {
$response = new ErrorResponse(ErrorCode::FORBIDDEN, "403", Response::HTTP_FORBIDDEN);
} catch (\LogicException $exception) { // display logic exceptions normally
throw $exception;
} catch (\Exception $exception) {
$response = new ErrorResponse(
ErrorCode::UNKNOWN,
$exception->getMessage() ?: "Unknown error occured",
Response::HTTP_INTERNAL_SERVER_ERROR
);
} catch (\Error $error) {
error_log($error->getMessage() ?: "Unknown error occured");
$response = new ErrorResponse(
ErrorCode::UNKNOWN,
$error->getMessage() ?: "Unknown error occured",
Response::HTTP_INTERNAL_SERVER_ERROR
);
}
// add cors headers to all responses
$response->headers->add($corsHeaders);
return $response;
}
}
|