133 lines
4.3 KiB
PHP
133 lines
4.3 KiB
PHP
<?php
|
|
|
|
namespace lib\Core;
|
|
|
|
use lib\Core\Http\Response;
|
|
|
|
class Router
|
|
{
|
|
private array $routes = [];
|
|
private string $currentGroupPrefix = '';
|
|
private array $currentGroupMiddlewares = [];
|
|
|
|
/**
|
|
* 라우트 등록 (동적 파라미터 지원)
|
|
*/
|
|
public function add(string $method, string $path, callable $callback, array $middlewares = []): void
|
|
{
|
|
// 그룹 접두사와 병합
|
|
$fullPath = trim($this->currentGroupPrefix . '/' . trim($path, '/'), '/');
|
|
|
|
$route = [
|
|
'method' => strtoupper($method),
|
|
'path' => $fullPath,
|
|
'callback' => $callback,
|
|
'middlewares' => array_merge($this->currentGroupMiddlewares, $middlewares)
|
|
];
|
|
|
|
// 동적 파라미터({param})가 있으면 정규식 패턴 생성
|
|
if (strpos($fullPath, '{') !== false) {
|
|
$pattern = preg_replace_callback('/\{(\w+)\}/', function ($matches) {
|
|
return '(?P<' . $matches[1] . '>[^/]+)';
|
|
}, $fullPath);
|
|
$route['pattern'] = '/^' . str_replace('/', '\/', $pattern) . '(\/.*)?$/';
|
|
} else {
|
|
// SEO key/value 형태를 위해 접두사 이후의 추가 파라미터를 허용
|
|
$route['pattern'] = '/^' . str_replace('/', '\/', $fullPath) . '(\/(?P<extra>.*))?$/';
|
|
}
|
|
|
|
$this->routes[] = $route;
|
|
}
|
|
|
|
/**
|
|
* 라우트 그룹: 공통 접두사와 공통 미들웨어를 적용
|
|
*/
|
|
public function group(string $prefix, callable $callback, array $middlewares = []): void
|
|
{
|
|
$previousGroupPrefix = $this->currentGroupPrefix;
|
|
$previousGroupMiddlewares = $this->currentGroupMiddlewares;
|
|
|
|
$this->currentGroupPrefix = trim($previousGroupPrefix . '/' . trim($prefix, '/'), '/');
|
|
$this->currentGroupMiddlewares = array_merge($previousGroupMiddlewares, $middlewares);
|
|
|
|
$callback($this);
|
|
|
|
$this->currentGroupPrefix = $previousGroupPrefix;
|
|
$this->currentGroupMiddlewares = $previousGroupMiddlewares;
|
|
}
|
|
|
|
/**
|
|
* 라우트 매칭 및 실행
|
|
*/
|
|
public function dispatch(string $uri, string $method = 'GET'): mixed
|
|
{
|
|
$uri = trim(parse_url($uri, PHP_URL_PATH), '/');
|
|
|
|
// 정적 파일 처리: public 폴더 내에 파일이 있으면 직접 서빙
|
|
$staticFile = DOCUMENTROOT_PATH . $uri;
|
|
if (file_exists($staticFile) && is_file($staticFile)) {
|
|
return $this->serveStaticFile($staticFile);
|
|
}
|
|
foreach ($this->routes as $route) {
|
|
if ($route['method'] !== strtoupper($method)) { //GET, POST 등 HTTP 메소드 체크
|
|
continue;
|
|
}
|
|
if (preg_match($route['pattern'], $uri, $matches)) {
|
|
$params = [];
|
|
// 패턴에 named 그룹이 있다면 추출
|
|
foreach ($matches as $key => $value) {
|
|
if (is_string($key)) {
|
|
$params[$key] = $value;
|
|
}
|
|
}
|
|
//추가 SEO key/value 파라미터로 파싱
|
|
if (isset($params['extra']) && $params['extra'] !== '') {
|
|
$extra = trim($params['extra'], '/');
|
|
$extraSegments = explode('/', $extra);
|
|
// 짝수개여야 key/value 쌍으로 변환 가능
|
|
if (count($extraSegments) % 2 === 0) {
|
|
for ($i = 0; $i < count($extraSegments); $i += 2) {
|
|
$key = $extraSegments[$i];
|
|
$value = $extraSegments[$i + 1];
|
|
$params[$key] = $value;
|
|
}
|
|
}
|
|
unset($params['extra']);
|
|
} else {
|
|
// 패턴에 extra 그룹이 없으면, 기본 GET 파라미터와 병합
|
|
$params = array_merge($_GET, $params);
|
|
}
|
|
return $this->handle($route, $params);
|
|
}
|
|
}
|
|
// 404 응답 객체 반환
|
|
return (new Response('해당 Route 경로를 찾을수 없습니다.' . $uri, 404));
|
|
}
|
|
|
|
/**
|
|
* 미들웨어 체인을 거쳐 최종 콜백 실행
|
|
*/
|
|
private function handle(array $route, array $params): mixed
|
|
{
|
|
$handler = $route['callback'];
|
|
$middlewares = $route['middlewares'];
|
|
|
|
$pipeline = array_reduce(
|
|
array_reverse($middlewares),
|
|
fn($next, $middleware) => fn($params) => (new $middleware)->handle($params, $next),
|
|
$handler
|
|
);
|
|
return $pipeline($params);
|
|
}
|
|
|
|
/**
|
|
* 정적 파일 서빙 (이미지, CSS, JS 등)
|
|
*/
|
|
private function serveStaticFile(string $file): Response
|
|
{
|
|
return (new Response())
|
|
->header('Content-Type', mime_content_type($file))
|
|
->setContent(file_get_contents($file));
|
|
}
|
|
}
|