PHP URL 라우팅 충돌 문제와 해결책

프리터코더·2025년 9월 19일
0

php 문제 해결

목록 보기
78/79

PHP URL 라우팅 충돌 문제와 해결책

PHP에서 URL 라우팅을 구현할 때 여러 라우트가 충돌하거나 예상과 다르게 동작하는 문제가 자주 발생합니다. 이런 문제들의 주요 원인과 해결책을 살펴보겠습니다.

1. 와일드카드 라우트 우선순위 문제

문제: 구체적인 라우트보다 와일드카드가 먼저 매칭되어 충돌

해결책:

// 구체적인 라우트를 먼저 정의
$routes = [
    '/admin/login' => 'AdminController@login',      // 구체적 라우트
    '/admin/users' => 'AdminController@users',      // 구체적 라우트
    '/admin/{action}' => 'AdminController@handle', // 와일드카드는 나중에
    '/{controller}/{action}' => 'handle'           // 가장 일반적인 것은 마지막
];

2. HTTP 메소드 구분 없는 라우팅

문제: 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');

3. 파라미터 패턴 충돌

문제: 비슷한 패턴의 라우트들이 잘못 매칭됨

해결책:

// 정규표현식으로 명확한 패턴 정의
$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;
}

4. 슬래시 처리 불일치

문제: 마지막 슬래시 유무로 인한 라우팅 실패

해결책:

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;
}

5. 다국어 URL 충돌

문제: 언어별 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 ?: '/'
        ];
    }
}

6. 중첩 라우트 그룹 충돌

문제: 라우트 그룹이 중첩되면서 경로가 꼬임

해결책:

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
    });
});

7. 동적 라우트 캐싱 문제

문제: 동적으로 생성된 라우트가 캐시되어 업데이트 안됨

해결책:

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';
    }
}

8. 미들웨어와 라우트 충돌

문제: 미들웨어 적용 순서로 인한 라우팅 문제

해결책:

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);
    }
}

9. API 버전별 라우팅 충돌

문제: 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');
});

10. 라우트 우선순위 자동 정렬

문제: 라우트 정의 순서에 따른 매칭 오류

해결책:

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} 순서
profile
일용직 개발자. freetercoder@gmail.com

0개의 댓글