서브버전 설정 · 10 min read · Jan 19, 2026
PHP 기반 웹사이트를 위한 모듈형 서브버전 저장소 설정
PHP 기반 웹사이트를 위한 모듈형 서브버전 저장소 설정
Willem Bogaerts - Kratz Business Solutions
요약
프로젝트 간 코드 공유는 여전히 서브버전으로 간단한 문제가 아닙니다. 특히 SourceSafe에 익숙하다면 서브버전이 코드를 공유하기 어렵게 만든다는 것을 알게 될 것입니다. 서브버전은 버전 혼란을 만드는 데 정말 뛰어나고 이를 해결하는 데도 좋지만, 내가 소스 코드 관리를 필요로 하는 이유는 그러한 혼란을 방지하기 위해서입니다. 서브버전은 크게 개선될 수 있지만 불가능하지는 않습니다. 이 방법서는 서브버전 공유 메커니즘과 저장소가 가져오는 다른 문제를 고려한 디렉토리 설정을 보여줄 것입니다.
이 방법서의 규칙
여러 곳에서
서브버전이 무엇인지, 기본 사용법을 알고 있으며, 저장소를 가지고 있거나 생성할 수 있다고 가정합니다.
이 방법서는 저장소를 구성하는 “최고의“ 방법을 제공하지 않으며, 이는 귀하의 필요에 따라 다릅니다. 이 방법서는 이러한 결정 중 일부와 그것들이 저장소의 구조에 미치는 영향을 보여줌으로써 도움을 주는 것을 목표로 합니다.
서브버전의 코드 공유 방식
작업 복사본의 디렉토리는 svn:externals 속성을 정의하여 다른 저장소에 대한 링크를 포함할 수 있습니다. 이렇게 하면 링크된 저장소 디렉토리가 작업 복사본에 포함되지만 프로젝트 자체의 일부가 되지는 않습니다. 즉, 작업 복사본에서 모든 파일이 포함된 디렉토리를 볼 수 있지만 중앙 저장소에서는 볼 수 없습니다. 중앙 저장소에서는 속성만 볼 수 있습니다.
이 공유 방식에는 몇 가지 단점이 있습니다:
- 링크에 대해 절대 URL만 제공할 수 있으므로 저장소를 원활하게 마이그레이션하는 것은 거의 불가능합니다. 프로토콜을 전환하는 것(예: svn://에서 https://로)조차도 깨진 작업 복사본과 많은 문제를 초래합니다.
- 파일을 링크할 수 없고, 디렉토리만 링크할 수 있습니다. 이는 코드 조직에 큰 영향을 미칩니다.
PHP 디렉토리 문제
웹사이트에서 디렉토리 작업을 할 때, 특히 PHP와 관련하여 고려해야 할 몇 가지 사항이 있습니다. 보안 문제로 인해 브라우저에서 접근할 수 있는 디렉토리에 모든 소스를 두고 싶지 않습니다. 브라우저에서 호출해야 하는 파일만 그곳에 두겠습니다. 이러한 파일을 “실행” 파일이라고 부르며, 클래스 정의나 함수 정의만 포함된 “정의“ 파일과 구분됩니다. 실행 코드와 정의 코드를 하나의 파일에 혼합하는 것은 일반적으로 좋은 생각이 아닙니다.
또한 보안 문제로 인해 많은 사이트에는 단위 테스트, 오류 로그 페이지 또는 심지어 전체 백오피스 사이트를 포함하는 제한된 영역이 있습니다. 이 제한된 영역은 일반적으로 웹 서버에 의해 비밀번호로 보호됩니다.
따라서 프로젝트에는 정의 파일이 있는 디렉토리와 실행 파일이 포함된 웹 루트 디렉토리(종종 www/ 또는 htdocs/)가 포함되며, 선택적으로 제한된 영역이 포함됩니다.
PHP와 상대 디렉토리
안타깝게도 PHP는 포함된 파일의 위치를 결정하는 매우 직관적이지 않은 방법을 가지고 있습니다. 파일 포함 명령은 모두 첫 번째 호출된 파일을 기준으로 작동하며, 현재 파일을 기준으로 작동하지 않습니다. 상황을 더욱 악화시키는 것은 원래 위치가 존재하는 파일로 이어지지 않을 경우 현재 파일 위치가 사용된다는 것입니다.
이것은 복잡하게 들리며, 실제로도 그렇습니다. 예를 들어 보겠습니다:
“index.php”라는 페이지를 호출한다고 가정해 보겠습니다. 이 페이지는 “library/functions.php”라는 페이지를 포함합니다. 이 페이지는 다시 “settings.php”를 포함합니다. “settings.php”가 라이브러리 디렉토리에서 검색될 것이라고 생각할 수 있지만, 그렇지 않습니다. “index.php”와 동일한 디렉토리에서 검색됩니다!
위에서 언급했듯이, PHP는 파일을 찾지 못하면 예상 디렉토리에서 계속 검색합니다. 따라서 모든 것이 예상대로 작동하는 것처럼 보이지만, 서로 다른 디렉토리에 동일한 이름의 파일이 있을 경우 문제가 발생합니다. 그러면 PHP가 “갑자기” 잘못된 디렉토리에서 파일을 선택하는 이유를 찾는 것이 매우 어려워집니다.
이는 상대 경로로 다른 파일을 안전하게 포함할 수 없음을 의미합니다. 우리는 다음과 같은 코드로 절대 경로를 만들어야 합니다:
require_once(dirname(__FILE__) . '/library/functions.php');상대 경로의 시작 부분에 ‘/‘를 잊지 마십시오. dirname 함수는 후행 슬래시 없이 경로를 반환합니다.
우리의 저장소 조직
프로젝트의 코드에 대해 고려해야 할 몇 가지 사항이 있습니다. 개발을 위해 전체 프로젝트를 통합하여 체크아웃하는 것이 편리합니다. 그러나 라이브 서버의 경우, 이것이 원하는 것이 아닐 수 있습니다. 웹 디렉토리와 멀리 떨어진 디렉토리에서 데이터베이스 코드(SQL 스크립트)를 체크아웃하고 싶을 수 있습니다. 서버에 전혀 두고 싶지 않은 프로젝트의 일부가 있을 수 있지만, 개발에는 필요할 수 있습니다. 예를 들어 문서 파일과 같은 것입니다.
또한 공유 코드를 저장할 중앙 장소가 필요합니다. 이론적으로 다른 프로젝트에서 코드를 “빌려”올 수 있지만, 어떤 프로젝트가 어떤 다른 프로젝트에 의존하는지를 추적하는 것은 정말 어렵습니다. 대신, 모든 표준 코드를 중앙 위치로 이동할 것입니다.
루트 디렉토리
모든 표준 코드를 포함하는 중앙 위치는 “
브랜치 및 태그
서브버전 저장소에서 코드를 “trunk”라는 브랜치에 보관하고 필요에 따라 동일한 수준에서 다른 브랜치를 만드는 것이 좋은 관행입니다. 브랜치를 원하지 않더라도, 프로젝트 바로 아래에 “trunk”라는 디렉토리를 만드십시오. Trunk는 활성 브랜치입니다.
이 점을 생각해 보십시오. 표준 라이브러리의 trunk에 링크할 때, 우리는 활성 브랜치에 링크하는 것입니다. 이는 링크된 라이브러리의 모든 오류 수정이 작업 복사본을 업데이트할 때마다 업데이트된다는 것을 의미합니다. 그러나 오류를 도입하면, 이는 우리의 작업 복사본에도 업데이트됩니다. 심지어 라이브 웹 서버의 작업 복사본에서도요! 대신 더 안정적인 브랜치에 링크할 수 있지만, 오류 수정을 위해서는 약간의 추가 작업이 필요합니다.
어떤 링크를 선택하든, 나중에 언제든지 전환할 수 있다는 것을 아는 것이 좋습니다.
구성 요소 또는 프로젝트에 포함된 것
저장소에 넣고 싶은 것들이 많으며, 모든 것을 웹 서버의 동일한 위치에 두고 싶지 않습니다. 어떤 것들은 웹 서버에 두지 않는 것이 가장 좋거나, 데이터베이스 서버와 같은 다른 서버에 체크아웃될 수 있습니다.
개발 머신에서 작업 복사본을 설정하는 것은 서버에서 설정하는 것과 다릅니다. 개발 머신에서는 모든 프로젝트에 대한 중앙 루트 디렉토리가 있을 것입니다. 이 디렉토리는 localhost 웹 서버를 통해 접근 가능하도록 구성되므로, 작업 중인 각 프로젝트에 대해 서버를 재구성할 필요가 없습니다. 라이브 서버에서는 어쨌든 구성이 필요하며, 보안 고려 사항으로 인해 필요한 파일만 체크아웃하고 그 이상은 하지 않습니다. 시작으로, 각 구성 요소에 다음 디렉토리를 만듭니다(필요한 경우):
documentation/고객 입력, 데이터베이스 및 객체 스키마 등. 웹 서버에 두지 않아도 되지만, 개발자에게 매우 유용합니다.sql/데이터베이스 생성, 업데이트 및 변환 스크립트. 데이터베이스 서버에서 체크아웃합니다.code/실제 애플리케이션 코드.test/단위 테스트
이 디렉토리는 프로젝트에서 대략 동일하게 나타나며, 단위 테스트는 실행 가능해야 하므로 코드 섹션의 일부입니다. 라이브 서버에 단위 테스트가 존재하지 않기를 원한다면, 이를 분리할 수 있습니다. 프로젝트의 경우, 내 설정은 다음과 같습니다:
documentation/위와 동일. 사용된 구성 요소의 참조도 포함됩니다.database/데이터베이스 생성 스크립트 및 참조를 포함합니다.sql/사용된 구성 요소의 디렉토리.web/정의 애플리케이션 코드.web/htdocs/실제 실행 애플리케이션 코드, HTML 페이지 및 스타일시트 및 이미지와 같은 기타 웹 콘텐츠가 포함된 사이트 루트.web/htdocs/restricted/제한된 영역.web/test/단위 테스트. 사용된 구성 요소의 테스트에서 참조를 포함합니다.selenium/기능 테스트 (자세한 내용은 http://selenium.openqa.org/ 참조).
실행 코드와 정의 코드 (다시)
실행 코드와 정의 코드를 별도의 디렉토리에 두어 실행 코드를 웹 루트 내의 별도의 디렉토리에 체크아웃할 수 있습니다. 그러나 이는 해당 코드를 포함하는 하위 디렉토리가 URL의 일부가 된다는 것을 의미합니다(예: www.example.com/restricted/errorhandling/viewerrorlog.php). 이는 원하지 않을 수 있습니다(예: www.example.com/restricted/viewerrorlog.php). 이에 대한 해결책이 있습니다. 실행 파일을 접근할 수 없는 장소에 두고 접근할 수 있는 장소에 일종의 프록시를 두는 것입니다. 이 프록시는 포함 또는 요구 문장만 있는 PHP 파일로, 접근할 수 없는 위치의 파일을 가리킵니다:
/htdocs/restricted/viewerrorlog.php로 호출됩니다.
// 이는 /errorhandling/viewerrorlog.php를 가리키는 프록시입니다. 브라우저에서 직접 호출할 수 없습니다.
// (웹 루트 외부에 있기 때문에)
require(dirname(__FILE__) . '/../../errorhandling/viewerrorlog.php');
?> 디렉토리 errorhandling은 표준 라이브러리에서 공유되며, 오류를 처리하기 위한 “정의“ 클래스 파일과 이를 보기 위한 “실행” 파일을 포함합니다.
머신 종속 설정
설정 파일은 저장소에서 다소 비정상적입니다. 저장소에 두고 싶지만 자동으로 업데이트되기를 원하지 않습니다. 나는 내 설정 파일에 대해 중간 방법을 사용합니다: 설정 디렉토리에 settings_example.php라는 파일을 생성합니다. 이 파일에는 모든 설정과 다양한 머신에 대해 설정하는 방법에 대한 주석이 포함되어 있습니다. 또한 “settings.php”라는 파일로 복사해야 한다는 주석도 포함되어 있습니다.
나는 다른 파일에서 settings.php만 참조하며, 설정 디렉토리에 “settings.php”의 값으로 svn:ignore 속성을 설정합니다. 이는 작업 복사본이 “상자에서 나오는 대로” 실행되지 않지만, 머신 종속 설정에 대한 의존성 때문에 어쨌든 실행되지 않을 것입니다. svn:ignore 속성은 업데이트 시 다른 머신의 설정이 귀하의 설정을 덮어쓰지 않도록 방지합니다.
실제 적용하기
예를 들어 보겠습니다. 메일을 보내고 PDF 파일을 생성해야 하는 프로젝트가 있다고 가정해 보겠습니다. 우리는 PEAR의 PHPMailer와 FPDF 라이브러리를 사용하기로 결정했습니다. PHP 설정에 특별한 제한을 두지 않기 위해, 우리는 PHPMailer를 다운로드하고 “pear install” 방법을 사용하지 않습니다.
또한 이 프로젝트에는 표준 오류 처리 및 표준 데이터베이스 클래스가 있을 것입니다. 단순화를 위해 빈 저장소에서 시작한다고 가정하겠습니다.
필요한 디렉토리 생성
먼저 위에서 설명한 루트 디렉토리를 생성합니다:
먼저 외부 라이브러리를 다루겠습니다. PHPMailer와 FPDF를 다운로드하고 압축을 풀고, 각각
다음으로 작업할 프로젝트를 생성합니다. 이 프로젝트는 고객 “CustomerInc”를 위한 것이며, 프로젝트 이름은 “SamplePrj”입니다. 귀하의 프로젝트에 대해 더 나은 이름을 생각해 낼 수 있다고 확신합니다. 따라서
웹 디렉토리 아래에 웹 루트(htdocs)와 일반 프로젝트별 php 파일을 위한 디렉토리를 생성했습니다.
공유 생성
지금까지는 저장소에서 직접 모든 작업을 했습니다. 그러나 코드를 공유하려면 먼저 작업 복사본을 만들어야 합니다. 그러니 그렇게 해봅시다.
우리의 localhost 웹 서버가 모든 프로젝트 코드를 접근할 수 있어야 하므로, 내 작업 복사본을 파일 시스템의 루트 폴더(/projects/
작업 복사본에는 웹 디렉토리가 있으며, htdocs 디렉토리가 위치하고 FPDF 및 PHPMailer에 대한 링크를 원하는 곳입니다. 이러한 링크를 생성하려면 웹 디렉토리에 다음 값을 가진 서브버전 속성 svn:external을 추가합니다:
fpdf /external/fpdf/trunk/code
phpmailer /external/phpmailer/trunk/code (예, 값은 2줄로 구성됩니다) 그런 다음 작업 복사본을 업데이트합니다. 이제 업데이트 시 프로젝트의 작업 복사본에서 두 개의 추가 디렉토리를 가져와야 합니다.
참고: 나는 trunk에 대한 링크를 생성했습니다. 대신 브랜치에 링크하거나 특정 수정 번호에 링크할 수 있습니다. 나는 코드 섹션에 대한 외부 참조만 생성했습니다. 외부 라이브러리의 문서에 대한 외부 참조도 생성하고 싶을 것입니다.
유용한 코드를 표준 라이브러리로 이동
프로젝트가 성숙해짐에 따라, 다른 프로젝트에서 유용할 코드가 점점 더 많이 개발됩니다. 우리의 경우, 오류 처리 패키지와 데이터베이스 클래스를 개발했다고 가정해 보겠습니다. 물론, 첫 번째 단계는 이 코드에서 모든 프로젝트 종속성을 제거하는 것입니다. 공유할 코드를 별도의 디렉토리에 두십시오. 이는 표준 라이브러리에서 다시 공유될 때 별도의 디렉토리에 있어야 합니다. 따라서 일반 오류 처리 코드는 errorhandling 디렉토리에 두고 데이터베이스 클래스는 database 디렉토리에 두어야 하며, 두 디렉토리는 모두 웹 디렉토리 바로 아래에 있어야 합니다.
svn mkdir 및 svn move 명령어 또는 그래픽 서브버전 클라이언트를 사용하여 프로젝트의 패키지를
서버에서 변경할 때마다 작업 복사본을 업데이트하면 아무 문제도 발생하지 않습니다. 문제는 여전히 원래 프로젝트 위치에 패키지가 있는 작업 복사본이 있고, 이제 동일한 디렉토리가 svn:externals 참조에서 오는 상태로 업데이트하려고 할 때 발생합니다. 이를 시도하면 외부 참조의 대상 디렉토리가 이미 존재하므로 외부 참조를 생성할 수 없다는 오류가 발생합니다. 이 문제는 해당 디렉토리를 수동으로 제거한 다음 작업 복사본을 업데이트하여 쉽게 해결할 수 있습니다.
단위 테스트, 문서, SQL 스크립트 등을 표준 라이브러리로 유사한 방식으로 이동하십시오.
라이브 서버 업데이트
웹 또는 데이터베이스 서버에 여러 사람이 접근할 수 있는 경우, 모든 사람을 위해 하나의 계정을 사용하는 것이 좋습니다. 이렇게 하면 .svn 디렉토리가 충돌하는 계정 설정 및 사용자 권한으로 인해 영향을 받지 않습니다. 또한 해당 계정에 저장소에 대한 쓰기 권한을 거부할 수 있으므로, 손상된 웹 서버가 악성 코드를 쉽게 커밋할 수 없습니다.
Linux 웹 서버에서는 다음 권한이 의미가 있습니다: “라이브” 작업 복사본을 업데이트하는 사용자에게 읽기 및 쓰기 권한, 그룹(웹 서버의 그룹)에는 읽기 권한, 다른 사용자에게는 권한 없음. 디렉토리에 SGID 비트를 설정하면 추가된 모든 파일도 웹 서버에서 접근할 수 있습니다. chmod g+s
drwxr-s--- 9 webdev abyssd 4096 2007-09-08 12:42 restricted/
-rw-r----- 1 webdev abyssd 441 2007-09-08 12:42 index.php
-rw-r----- 1 webdev abyssd 2954 2007-09-08 12:42 main.css모듈형 데이터베이스 코드
우리는 이미 PHP에서 포함이 어떻게 작동하는지 논의했지만, SQL에서 파일을 어떻게 포함합니까? 우리는 SQL 코드에서 서로 다른 디렉토리의 서로 다른 파일을 포함할 수 있어야 합니다. PHP 코드를 공유하는 경우, 해당 SQL 코드도 공유해야 합니다. 이를 위해 작은 PHP 및 파이썬 스크립트를 작성했습니다. 이들은 http://www.w-p.dds.nl/sqlincludeparser_php.txt (PHP) 및 http://www.w-p.dds.nl/sqlincludeparser_py.txt (Python)에서 찾을 수 있습니다. 이 스크립트는 다음 형식의 기본 파일에서 포함 문을 정의할 수 있게 해줍니다:
CREATE DATABASE IF NOT EXISTS someDatabase;
USE someDatabase;
-- @include(errorhandling/errortables.sql) # (재)생성 테이블은 오류 처리 패키지에서 사용됩니다.
DROP TABLE IF EXISTS someTable;
CREATE TABLE someTable
(someID INTEGER UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
...기타.모든 포함된 파일과 기본 파일을 반복 가능하게 만들면, 이를 사용하여 깨끗한 상태로 반복적으로 돌아갈 수 있습니다. 이는 테스트 및 개발에 매우 유용할 수 있습니다. 이를 사용하려면, 내 스크립트의 출력을 SQL 명령줄 클라이언트로 파이프합니다:
sqlincludeparser.py | mysql -u -p [] 새 게시물을 받은 편지함에서 받기
스팸은 없습니다. 언제든지 구독 해지 가능합니다.