PHP로 프로젝트를 만들다 보면 거의 한 번은 마주치는 오류가 있습니다.

Warning: require_once(.../config/config.php): failed to open stream: No such file or directory
Fatal error: require_once(): Failed opening required '.../config/config.php'

분명 파일은 있는데 "없다"고 나옵니다. 원인은 대부분 경로 계산 깊이 착오입니다. 이 글에서 원인과 안전한 해결 패턴을 정리합니다.

원인: dirname() 단계가 한 칸 어긋난다

PHP에서 현재 파일이 있는 폴더는 __DIR__, 그 부모 폴더는 dirname(__DIR__)로 거슬러 올라갑니다. 한 번 호출할 때마다 한 단계씩 위로 갑니다.

파일 위치: /var/www/blog/admin/posts/form.php

__DIR__                       = /var/www/blog/admin/posts
dirname(__DIR__)              = /var/www/blog/admin
dirname(dirname(__DIR__))     = /var/www/blog        ← 프로젝트 루트
dirname(dirname(dirname(...)))= /var/www             ← 한 칸 더 가버림(오류!)

루트가 /var/www/blog인데 dirname을 한 번 더 부르면 /var/www가 되고, 거기에 /config/config.php를 붙이니 존재하지 않는 경로가 되는 것입니다.

해결: 진입 파일마다 깊이를 정확히 맞춘다

각 PHP 파일이 루트에서 몇 단계 아래에 있는지 세고, 그 수만큼만 dirname을 호출합니다.

파일 위치루트까지 깊이ROOT_PATH 정의
/blog/index.php0단계__DIR__
/blog/admin/dashboard.php1단계dirname(__DIR__)
/blog/admin/posts/form.php2단계dirname(dirname(__DIR__))
// /blog/admin/posts/form.php  (루트에서 2단계 아래)
define('ROOT_PATH', dirname(dirname(__DIR__)));   // = /blog
require_once ROOT_PATH . '/config/config.php';
require_once dirname(__DIR__) . '/_auth.php';     // = /blog/admin/_auth.php

디버깅 팁: 일단 찍어본다

경로가 의심되면 추측하지 말고 실제 값을 출력해 보세요.

echo ROOT_PATH;        // 기대한 루트가 맞는지
var_dump(file_exists(ROOT_PATH . '/config/config.php'));

file_exists()false면 경로가 틀린 것이고, 출력된 ROOT_PATH가 한 칸 위/아래인지 바로 보입니다.

정리

  • 오류 메시지에 찍힌 실제 경로를 먼저 본다 — 어디를 잘못 가리키는지 답이 거기 있다.
  • dirname 호출 횟수 = 파일이 루트에서 떨어진 단계 수.
  • 진입 파일이 여러 깊이에 흩어져 있으면, 각 파일의 깊이를 개별로 맞춰야 한다. 한 패턴을 복붙하면 깊이가 다른 파일에서 깨진다.

같은 오류를 만나면 메시지의 경로부터 확인하세요. 90%는 거기서 끝납니다.