상황 (Laravel 8 경험 사례)
오랜만에 새로운 프로젝트를 Laravel 8로 시작.
테스트 코드를 먼저 작성하는 방식으로 개발해보려 했는데, 시작부터 막혔다.
라우트에 다음과 같이 컨트롤러를 연결하고 테스트를 실행했다.
Route::get('/', 'BaseController@index')->name('site-index');
그랬더니:
Expected response status code [200] but received 500.
The following exception occurred during the request:
ReflectionException: Class "BaseController" does not exist in ...
컨트롤러를 못 찾는다는 게 이해가 안 됐다.
Laravel 5.x를 쓸 때는 문자열 컨트롤러 지정('BaseController@index')이 아무 문제 없이 동작했기 때문이다.
원인
Laravel 8부터 RouteServiceProvider에 하드코딩되어 있던 컨트롤러 네임스페이스 자동 prefix 처리($namespace = 'App\Http\Controllers')가 기본적으로 빠졌다.
그래서 'BaseController@index'처럼 짧은 이름만 써주면, Laravel이 App\Http\Controllers 네임스페이스를 자동으로 붙여주지 못한다.
결국 BaseController라는 클래스를 최상위 네임스페이스에서 찾다가 실패하는 것이다.
구글링해보니 해외 커뮤니티(Stack Overflow)에 이미 잘 정리되어 있었다.
해결 방법 3가지
1. 컨트롤러 클래스 이름 앞에 네임스페이스 전체 경로를 포함한다.
Route::get('/', 'App\Http\Controllers\BaseController@index')->name('site-index');
2. use 키워드로 클래스를 임포트하고 배열(튜플) 문법을 사용한다.
use App\Http\Controllers\BaseController;
Route::get('/main', [BaseController::class, 'index'])->name('site-index');
3. RouteServiceProvider에 컨트롤러 네임스페이스를 다시 설정한다. (가장 간편했던 방법)
// app/Providers/RouteServiceProvider.php
protected $namespace = 'App\\Http\\Controllers';
컨트롤러 네임스페이스가 기본적으로 설정은 되어 있었지만 주석 처리되어 있었다.
버전별로 이 부분의 변화가 좀 복잡했다.
- 7.x까지: $namespace 기본 적용
- 8.0.0: $namespace 변수 자체가 제거됨
- 8.0.1: $namespace = null로 되돌아옴
- 8.0.2부터: 기본값 세팅 + 주석 처리 상태로 유지
IDE에서 컨트롤러 클래스로 코드 추적(점프)이 되는지를 생각하면, 2번 방법(임포트 + 배열 문법)에 익숙해지는 게 장기적으로 나을 것 같다.
업데이트: Laravel 9 이후에는 상황이 더 바뀌었다
이 글은 Laravel 8 기준으로 작성된 경험담이다.
이후 버전을 쓰는 분들을 위해 몇 가지 추가로 정리해둔다.
에러 메시지 자체가 바뀌었다
최신 버전에서는 ReflectionException: Class "..." does not exist 대신 아래 형태로 나온다.
Target class [BaseController] does not exist.
원인은 동일(컨트롤러 네임스페이스를 못 찾음)하니 해결 방법도 그대로 적용된다.
3번 방법(RouteServiceProvider의 $namespace 설정)은 Laravel 11부터 아예 불가능해졌다
Laravel 11에서 app/Providers/RouteServiceProvider.php 파일 자체가 삭제되고, 라우팅 관련 설정은 bootstrap/app.php의 withRouting()으로 옮겨갔다.
AuthServiceProvider, EventServiceProvider, Http/Kernel.php도 같은 흐름으로 사라지거나 AppServiceProvider/bootstrap/app.php로 흡수됐다.
즉 지금 최신 버전(9, 10, 11, 12.x)을 쓰고 있다면 3번 방법은 선택지에서 제외해야 한다.
결과적으로 남는 선택지는 1번(풀 네임스페이스 문자열) 또는 2번(임포트 + 배열 문법)이다.
IDE 연동이나 리팩터링 편의성을 생각하면 2번이 사실상 표준으로 굳어졌다.
최신 Laravel 스타터 킷이나 공식 문서 예제들도 대부분 이 방식을 기본으로 사용한다.
정리하면:
- Laravel 9 이하 & RouteServiceProvider 파일이 남아있는 프로젝트: 위 세 가지 방법 모두 유효
- Laravel 11 이상: 1번 또는 2번만 가능, 2번을 권장
Laravel 8 당시 작성한 글을 다시 정리했습니다.