PHP에서 URL 라우팅을 구현할 때 여러 라우트가 충돌하거나 예상과 다르게 동작하는 문제가 자주 발생합니다. 이런 문제들의 주요 원인과 해결책을 살펴보겠습니다.
문제: 구체적인 라우트보다 와일드카드가 먼저 매칭되어 충돌
해결책:
// 구체적인 라우트를 먼저 정의
$routes = [
'/admin/login' => 'AdminController@login', // 구체적 라우트
'/admin/users' => 'AdminController@users', // 구체적 라우트
'/admin/{action}' => 'AdminController@handle', // 와일드카드는 나중에
'/{controller}/{action}' => 'handle' // 가장 일반적인 것은 마지막
];
문제: GET과 POST 요청이 같은 URL에서 충돌
해결책:
class Router {
private $routes = [];
public function get($path, $handler) {
$this->routes['GET'][$path] = $handler;
}
public function post($path, $handler) {
$this->routes['POST'][$path] = $handler;
}
public function resolve() {
$method = $_SERVER['REQUEST_METHOD'];
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
return $this->routes[$method][$path] ?? null;
}
}
$router = new Router();
$router->get('/users', 'UserController@index');
$router->post('/users', 'UserController@store');
문제: 비슷한 패턴의 라우트들이 잘못 매칭됨
해결책:
// 정규표현식으로 명확한 패턴 정의
$routes = [
'/users/(\d+)' => 'UserController@show', // 숫자만
'/users/([a-zA-Z]+)' => 'UserController@page', // 문자만
'/users/create' => 'UserController@create' // 고정 문자열
];
function matchRoute($uri) {
global $routes;
foreach ($routes as $pattern => $handler) {
if (preg_match('#^' . $pattern . '$#', $uri, $matches)) {
return ['handler' => $handler, 'params' => array_slice($matches, 1)];
}
}
return null;
}
문제: 마지막 슬래시 유무로 인한 라우팅 실패
해결책:
function normalizeUrl($url) {
// 마지막 슬래시 제거 (루트 경로 제외)
$url = rtrim($url, '/');
return $url === '' ? '/' : $url;
}
function findRoute($uri) {
$uri = normalizeUrl($uri);
$routes = [
'/' => 'HomeController@index',
'/about' => 'PageController@about',
'/contact' => 'PageController@contact'
];
return $routes[$uri] ?? null;
}
문제: 언어별 URL이 기본 라우트와 충돌
해결책:
class MultiLanguageRouter {
private $languages = ['ko', 'en', 'ja'];
public function resolve($uri) {
$segments = explode('/', trim($uri, '/'));
$lang = 'ko'; // 기본 언어
// 첫 번째 세그먼트가 언어 코드인지 확인
if (in_array($segments[0], $this->languages)) {
$lang = array_shift($segments);
}
$path = '/' . implode('/', $segments);
return [
'language' => $lang,
'path' => $path ?: '/'
];
}
}
문제: 라우트 그룹이 중첩되면서 경로가 꼬임
해결책:
class RouteGroup {
private $routes = [];
private $prefix = '';
public function group($prefix, $callback) {
$oldPrefix = $this->prefix;
$this->prefix .= $prefix;
$callback($this);
$this->prefix = $oldPrefix; // 원복
}
public function get($path, $handler) {
$fullPath = $this->prefix . $path;
$this->routes['GET'][$fullPath] = $handler;
}
}
$router = new RouteGroup();
$router->group('/admin', function($r) {
$r->get('/users', 'AdminUserController@index'); // /admin/users
$r->group('/settings', function($r) {
$r->get('/general', 'SettingsController@general'); // /admin/settings/general
});
});
문제: 동적으로 생성된 라우트가 캐시되어 업데이트 안됨
해결책:
class CachedRouter {
private $cacheFile = 'routes.cache';
public function getRoutes() {
if (file_exists($this->cacheFile) && !$this->isDevelopment()) {
return unserialize(file_get_contents($this->cacheFile));
}
$routes = $this->buildRoutes();
file_put_contents($this->cacheFile, serialize($routes));
return $routes;
}
private function isDevelopment() {
return defined('APP_ENV') && APP_ENV === 'development';
}
}
문제: 미들웨어 적용 순서로 인한 라우팅 문제
해결책:
class MiddlewareRouter {
public function addRoute($path, $handler, $middleware = []) {
return [
'path' => $path,
'handler' => $handler,
'middleware' => $middleware
];
}
public function handle($route, $request) {
// 미들웨어 먼저 실행
foreach ($route['middleware'] as $middleware) {
if (!$this->runMiddleware($middleware, $request)) {
return false; // 미들웨어에서 차단
}
}
// 핸들러 실행
return call_user_func($route['handler'], $request);
}
}
문제: API 버전이 다른 같은 엔드포인트가 충돌
해결책:
class ApiRouter {
public function version($version, $callback) {
$prefix = "/api/v{$version}";
$routes = [];
$callback(function($path, $handler) use ($prefix, &$routes) {
$routes[$prefix . $path] = $handler;
});
return $routes;
}
}
$router = new ApiRouter();
// v1 API
$v1Routes = $router->version('1', function($route) {
$route('/users', 'V1\UserController@index');
});
// v2 API
$v2Routes = $router->version('2', function($route) {
$route('/users', 'V2\UserController@index');
});
문제: 라우트 정의 순서에 따른 매칭 오류
해결책:
function sortRoutes($routes) {
// 구체적인 라우트일수록 높은 우선순위
uksort($routes, function($a, $b) {
$aScore = substr_count($a, '/') - substr_count($a, '{');
$bScore = substr_count($b, '/') - substr_count($b, '{');
return $bScore - $aScore; // 내림차순
});
return $routes;
}
$routes = [
'/{controller}/{action}' => 'genericHandler',
'/admin/users' => 'specificHandler',
'/admin/{action}' => 'adminHandler'
];
$sortedRoutes = sortRoutes($routes);
// 결과: /admin/users, /admin/{action}, /{controller}/{action} 순서