여러 레거시 PHP 프로젝트를 인수받아 운영한 경험이 있다. CodeIgniter 1.x부터 3.x, Laravel 5.x 시대의 코드베이스들이었다. 문서가 없고, 이전 개발자에게 인수인계를 받을 수 없는 상황이 대부분이었다. 그런 경우에 가장 먼저 하는 것들을 정리한다.
Table of contents
Open Table of contents
1. 코드베이스 전체 파악
디렉토리 구조 확인
가장 먼저 프로젝트의 뼈대를 파악한다.
# 디렉토리 구조 파악 (2단계까지)
find . -type d -maxdepth 2 | head -50
# 파일 확장자별 통계
find . -type f | sed 's/.*\.//' | sort | uniq -c | sort -rn | head -20
# PHP 파일 수
find . -name "*.php" | wc -l
# 프레임워크 식별
ls -la composer.json 2>/dev/null
cat composer.json | grep -E "(codeigniter|laravel|symfony|yii)"
프레임워크를 식별하는 것이 첫 번째다. CodeIgniter는 system/ 디렉토리와 application/ 구조로, Laravel은 artisan 파일과 app/ 디렉토리로 구별된다. 간혹 프레임워크 없이 순수 PHP로 작성된 프로젝트도 있다 — 이 경우가 가장 난이도가 높다.
진입점 파악
# 웹 진입점
cat public/index.php 2>/dev/null || cat index.php
# 라우팅 설정
find . -name "routes.php" -o -name "web.php" -o -name "routes" -type d
# .htaccess 또는 nginx 설정에서 rewrite 규칙 확인
cat .htaccess 2>/dev/null
CodeIgniter 1.x~2.x는 application/config/routes.php에 라우팅이 있지만, 실제로는 컨트롤러명이 곧 URL인 경우가 많다. 라우팅 파일에 정의되지 않은 URL이 동작하는 경우가 있으므로, 컨트롤러 디렉토리를 직접 확인해야 한다.
코드 규모 측정
# 유효 코드 라인 수 (빈 줄, 주석 제외)
find . -name "*.php" -not -path "*/vendor/*" | xargs cat | grep -v '^\s*$' | grep -v '^\s*//' | grep -v '^\s*\*' | wc -l
코드 규모에 따라 이후 분석 전략이 달라진다. 5만 줄 이하면 전체를 읽을 수 있고, 그 이상이면 핵심 비즈니스 로직부터 선별적으로 파악한다.
2. 환경 구성 확인
PHP 버전 및 확장 모듈
php -v
php -m
# 프로젝트에서 사용하는 함수 중 특정 버전에서 제거된 것이 있는지
grep -rn "mysql_connect\|mysql_query\|ereg\|split(" --include="*.php" . | head -20
# mbstring, gd, curl 등 확장 의존성 확인
grep -rn "mb_\|imagecreate\|curl_init" --include="*.php" . | head -10
레거시 PHP 프로젝트에서 가장 흔한 문제는 PHP 버전 호환성이다. mysql_* 함수(PHP 7.0에서 제거), ereg 함수(PHP 7.0에서 제거), each() 함수(PHP 8.0에서 제거) 같은 것들이 여전히 사용되고 있는 경우가 많다.
설정 파일
# 환경 설정 파일 찾기
find . -name "*.env*" -o -name "database.php" -o -name "config.php" -o -name "db.php" | grep -v vendor
# 하드코딩된 접속 정보 확인
grep -rn "password\|passwd\|db_pass" --include="*.php" . | grep -v vendor | head -20
주의: 레거시 프로젝트에서는 DB 접속 정보가 소스코드에 하드코딩되어 있는 경우가 흔하다. 인수 직후 환경 변수로 분리하는 것을 최우선으로 처리한다.
3. 데이터베이스 스키마 분석
코드보다 DB 스키마가 비즈니스 로직을 더 정확히 보여줄 때가 많다.
-- 테이블 목록과 크기
SELECT table_name, table_rows,
ROUND((data_length + index_length) / 1024 / 1024, 2) AS size_mb,
engine, table_collation
FROM information_schema.tables
WHERE table_schema = DATABASE()
ORDER BY data_length + index_length DESC;
-- 외래키 관계 파악
SELECT table_name, column_name, referenced_table_name, referenced_column_name
FROM information_schema.key_column_usage
WHERE table_schema = DATABASE()
AND referenced_table_name IS NOT NULL;
-- 인덱스 현황
SELECT table_name, index_name, GROUP_CONCAT(column_name ORDER BY seq_in_index) AS columns
FROM information_schema.statistics
WHERE table_schema = DATABASE()
GROUP BY table_name, index_name;
체크 항목:
- 테이블 간 관계가 외래키로 정의되어 있는지, 아니면 애플리케이션 레벨에서만 관리되는지
auto_increment컬럼의 현재 값과 데이터 타입 상한 비교- 인덱스 없는 대형 테이블 존재 여부
- 캐릭터셋 혼용 여부 (
latin1,euckr,utf8,utf8mb4혼재)
레거시 프로젝트에서 캐릭터셋 혼용은 거의 100% 발견된다. 특히 한국어 프로젝트는 euckr과 utf8이 섞여 있는 경우가 많다.
4. 배포 프로세스 확인
# 배포 스크립트 존재 여부
find . -name "deploy*" -o -name "Makefile" -o -name "Capfile" -o -name "fabfile*"
# Git 설정 확인
git remote -v
git log --oneline -10
# 서버 직접 수정 여부 확인 (서버에서)
git status
git diff --stat
레거시 PHP 프로젝트의 배포 방식은 대체로 세 가지 중 하나다.
- FTP 업로드 — 가장 원시적이지만 가장 흔함
- 서버에서 git pull — 그나마 나은 경우
- 서버에서 직접 수정 — 최악의 경우
서버에서 직접 수정하는 방식이라면, 서버의 코드가 Git 저장소와 다를 수 있다. 이 경우 서버의 코드를 기준으로 저장소를 동기화하는 것이 우선이다.
5. 의존성 정리
Composer 의존성
# composer.json 존재 여부
cat composer.json
# 설치된 패키지 확인
composer show
# 보안 취약점 확인
composer audit
# 오래된 패키지 확인
composer outdated
CodeIgniter 2.x 이전 프로젝트는 Composer를 사용하지 않는 경우가 많다. 라이브러리가 application/libraries/ 또는 application/third_party/에 직접 복사되어 있다. 이 경우 각 라이브러리의 버전과 출처를 파악하는 것이 어렵다.
외부 라이브러리 직접 포함
# vendor 외부의 외부 라이브러리 찾기
find . -name "*.php" -path "*/libraries/*" -not -path "*/vendor/*"
find . -name "*.php" -path "*/third_party/*"
# PHPMailer, TCPDF, PHPExcel 등 흔한 레거시 라이브러리
grep -rl "PHPMailer\|TCPDF\|PHPExcel\|Spreadsheet" --include="*.php" . | grep -v vendor
6. PSR 준수 확인
# PSR-4 오토로딩 여부
grep -A5 "autoload" composer.json
# 파일명과 클래스명 불일치 확인 (PSR-4 위반)
grep -rn "^class " --include="*.php" . | head -20
CodeIgniter 1.x~2.x는 PSR과 무관한 자체 로딩 규칙을 사용한다. 클래스명의 첫 글자만 대문자로 하는 규칙이라 PSR-4와 호환되지 않는다. 즉시 전환할 필요는 없지만, 신규 코드는 PSR-4를 따르도록 점진적으로 변경한다.
7. 보안 취약점 스캔
레거시 PHP 프로젝트에서 발견되는 보안 문제는 대체로 예측 가능하다.
# SQL Injection 위험 코드
grep -rn "query.*\\\$_\|where.*\\\$_\|SELECT.*\\\$_" --include="*.php" . | grep -v vendor | head -20
# XSS 위험 코드
grep -rn "echo.*\\\$_\|print.*\\\$_" --include="*.php" . | grep -v vendor | head -20
# 파일 업로드 처리
grep -rn "move_uploaded_file\|\\\$_FILES" --include="*.php" . | grep -v vendor | head -10
# eval, exec 등 위험 함수
grep -rn "eval(\|exec(\|system(\|passthru(\|shell_exec(" --include="*.php" . | grep -v vendor
가장 흔한 취약점:
- Prepared Statement 미사용 (문자열 연결로 쿼리 생성)
- 사용자 입력 미검증 출력 (XSS)
- 파일 업로드 확장자/MIME 미검증
- CSRF 토큰 미사용
- 세션 고정 공격 미방어
발견된 취약점은 심각도 순으로 정리하고, SQL Injection과 인증 우회는 즉시 수정한다. XSS는 출력 레이어에서 일괄 처리하는 방안을 검토한다.
8. 로그 및 에러 핸들링
# 에러 핸들링 설정
grep -rn "error_reporting\|display_errors\|log_errors" --include="*.php" . | grep -v vendor | head -10
# 로그 파일 위치
find . -name "*.log" -o -name "log_*" | head -10
find /var/log -name "*php*" 2>/dev/null
운영 환경에서 display_errors = On인 채로 돌아가는 레거시 프로젝트가 놀라울 정도로 많다. 이것은 에러 메시지를 통해 서버 경로, DB 구조 등 내부 정보가 노출되는 심각한 보안 문제다.
인수 후 첫 2주 액션 플랜
모든 것을 한꺼번에 고칠 수 없다. 우선순위를 정해야 한다.
1주차 — 파악과 긴급 수정:
- 코드베이스 구조 파악 (프레임워크, 규모, 디렉토리)
- DB 스키마 ERD 생성 (도구 활용 또는 수동)
- 배포 프로세스 확인 및 Git 기반 배포 전환
- 하드코딩된 접속 정보 환경 변수로 분리
- SQL Injection 등 치명적 보안 취약점 즉시 수정
2주차 — 안정화:
- 에러 로깅 체계 구축 (
display_errors = Off, 파일 로깅) - 모니터링 설정 (최소한 서버 생존 여부 확인)
- DB 백업 자동화 확인 또는 설정
- 주요 비즈니스 로직 흐름 문서화 (코드 읽으며 작성)
- 기술 부채 목록 작성 (우선순위 포함)
핵심 정리
레거시 PHP 프로젝트 인수의 핵심은 “고치기 전에 이해하기”다. 코드를 수정하고 싶은 충동을 참고, 최소 1주는 현재 상태를 파악하는 데만 투자한다. 이해 없이 수정하면 기존에 동작하던 것마저 깨뜨린다.
가장 중요한 원칙: 레거시 코드는 “나쁜 코드”가 아니라 “맥락을 잃은 코드”다. 작성 당시에는 나름의 이유가 있었을 것이다. 그 이유를 추정하면서 읽으면, 코드의 의도가 보이기 시작한다.
참고 자료
- Docker Hub PHP Official Image — PHP 공식 Docker 이미지
- Migrating from CodeIgniter to Laravel — CodeIgniter에서 Laravel 마이그레이션 가이드