PHP & Others

나만의 규칙: include(), require(), include_once(), require_once()

페이지 정보

본문


밑에서 include(), require(), include_once(), require_once() 등에 대한 논의가
활발하게 벌어지고 있더군요..

제가 몇년간 php로 작업하면서 세운 나름대로의 규칙을 이야기하려고 합니다.
혹 괜찮은 아이디라고 생각하시면 참고하시고, 아니라고 생각하시면 사정없이 까주십쇼.

1. 'include 되는 파일' 의 구분

include 되는 파일은 기본적으로 2가지로 구분할 수 있습니다.

include  파일 자체를 하나의 함수처럼 만들어서 사용하는 경우가 있는데..
그다지 좋지 않은 방법입니다. include 파일 자체도 독자적으로 실행될 가능성이 얼마든지 존재하기 때문입니다. 그래서 HTML 출력용 include 와 로직 정의용 include를 분리하는거죠..

1) UI 컴포넌트

UI 출력을 단순화하거나 쉽게 관리하기 위한 것입니다.
기본적으로 HTML 코드의 구조화 또는 재활용을 위한 것입니다.

UI를 출력하는 과정에서 (a)전체 페이지의 HTML 코드가 너무 길어진다던가(관리하기 힘들어질정도로) (b)여러 페이지에서 사용되는경우, (c)하나의 위치에 경우에 따라 다양한 컴포넌트가 배치되어야 할 경우

(a)의 경우는 별도로 말 안해도 이해하실 거구요
(b)의 경우는 네비게이션 메뉴라던가 달력이라던가 등등
(c)의 경우는 예를 들면 탐색기의 큰 아이콘, 간단히, 자세히 보기 같은 경우죠

- 규칙1: HTML 출력에 관련된 코드만을 담는다.

DB 접근이나 프로그램 로직에 관련된 코드는 절대 담아서는 안됩니다.
변수의 출력 형태를 변환한다거나 (날자-시간 출력형식, 소수점 출력 형식 등등)
if - else 에 의해 출력을 조절하는 정도 이상의 코드가 들어가지 않도록 합니다.

- 규칙2: include()를 호출하는 페이지와 같은 디렉토리에 위치 시키거나, 호출하는 페이지를 기준으로 최대한 가깝게 저장될 위치를 설계한다.

보통 UI 컴포넌트에 해당되는 파일은 전체 사이트의 아주 일부분의
응용프로그램에서만 사용되는 경우가 많습니다.
또한 이런 경우 대부분 디렉토리가 나름대로의 체계에 따라 구분되어있을 것입니다.
그 체계에 따라 위치를 정하는 것이 여러모로 좋습니다.
(미리 말씀드리자면 이 규칙은 상대경로로 접근을 쉽게하기 위해서 입니다.)

2) 라이브러리 or 응용프로그램 모듈

자주 사용되는 유틸리티 함수들이나 시스템 전반의 변수, 상수 설정 등등..

저는 '라이브러리' 또는 '응용프로그램 모듈' 등으로 부릅니다. (이름이 좀 궁색합니다.)
라이브러리는 자주 사용되는 기능을 표준화 하여 정의해 놓은 함수/클래스의 집합이구요
응용프로그램 모듈은 특정 응용프로그램에서 UI 출력을 제외하고 처리로직만 분리해 놓은 것입니다.

- 규칙1: 전역 변수 선언 및 값 지정(상수같이 사용하는 전역변수들..), 함수/클래스 정의 이외의 코드는 포함하지 않는다.

이것은 어떤 악의적인 사용자가 라이브러리 파일의 이름을 알아내고 브라우저로 호출할 경우에도 아무동작도 수행되지 않게 하기 위한 것입니다. 그냥 변수좀 설정되고 함수좀 정의되고나서 페이지 처리가 끝나겠죠.

실제로 코드가 실행되기 위해서는 사용자 페이지에서 해당 함수를 호출해 줘야 합니다.
물론 호출되기 전에 전달인자등의 적합성을 검사하게 되겠죠.

- 규칙2: HTML 출력에 관련된 코드는 절대 포함하지 않는다.

모든 함수는 특정 자료구조를 입력으로 받아 특정 자료구조를 리턴하는 형태로 되어야 합니다.
리턴된 자료구조를 HTML 등의 형태로 변환하여 출력하는 것은 include 를 호출한 해당 페이지가 알아서 하도록 하는 것입니다.

이것은 프리젠테이션 과 비즈니스로직을 분리한다는 개념으로 받아들이시면 될거 같습니다.

응용프로그램 모듈을 이런식으로 출력과 아무 상관없도록 만들어 놓으면 cron 에 등록할 배치작업 등등에서도 손쉽게 불러다가 사용할 수 있습니다.

- 규칙3: 한군데다가 다 몰아서 저장한다.

라이브러리 파일이 저장될 위치를 한군데로 통일하고, 디렉토리 구분이 필요하면 그 밑에 디렉토리를 만들어 구분합니다.

저는 편의상 이런 디렉토리를 lib 라고 짓습니다. 대충 다음과 같은 형태가 됩니다.

/home/somesite.com
+    bin/    -- 각종 실행파일 (배치처리용 스크립트등)
+    conf/  -- 각종 설정파일(apache vhost 설정 등)
+    log/    -- 각종 로그파일
+    lib/      -- 라이브러리 파일
+    public_html/  -- 웹서버 (가상호스트) ROOT

(미리 말씀드리자면 이 규칙은 include_path를 쉽게 사용하기 위해서 입니다.)

include 되는 파일의 구분은 이렇게 딱 두가지 입니다.
간단하죠? 더 복잡하게 구분해 봤자 별 이득이 없었습니다.

2. 파일 작성 규칙

1) 모든 include 되는 파일은 '_' 로 시작하고 '.php' 로 끝난다.

'_' 로 시작하는 파일은 독자적으로 동작하는 것이 아니라 다른 파일에 의해서 사용되는 파일이란 의미입니다. (제가 나름대로 붙인 의미입니다.)

여기서 독자적으로 동작한다는 것은 웹브라우저에 의해 호출된다는 의미입니다.
javascript  파일등 HTML에 의존적이지만 이것들은 브라우저에 의해 호출되어져야 합니다.
따라서 javascript 파일은 '_'로 시작되어서는 안됩니다.

웹서버 설정에서 '_'로 시작하는 파일은 요청할수 없도록 규칙을 설정해 버릴수도 있습니다.
앞서 이야기한 악의적인 사용자들의 장난을 무력화하기 위해서죠. (그렇게 까지 안해도 상관없기는 합니다만..)

2) 라이브러리에서 정의되는 함수는 해당라이브러리 이름을 prefix로 붙인다.

예를들어 GetCommentList() 라는 함수가 _MediaComment.php 라는 라이브러리에 있다면
실제 함수의 이름은 MediaComment_GetCommentList() 가 되어야합니다.
이렇게 규칙을 지켜주면 require_once()와 함께 사용할 경우 함수명 중복의 문제를 피해 갈 수 있습니다.

3) 라이브러리에서 전역변수를 설정할 경우 반드시 global 선언을 한다.

이것은 함수에서 라이브러리를 로드할 경우에도 정확하게 동작하도록 하기 위한 것입니다.

<?
// _Some.php
global $SOME_ROOT;
$SOME_ROOT = '/some/root";

function Some_DoSome() {
    global $SOME_ROOT;
    print $SOME_ROOT;
}
?>

<?
// _UseSome.php
function UseSome_DoSome()
{
    require_once "_Some.php";
    Some_DoSome();
}
?>

_Some.php 에서 $SOME_ROOT 전역변수를 global 선언을 안할경우
_Some.php 가 함수내에서 호출된 경우 $SOME_ROOT 는 호출한 함수의 지역변수가 되어버립니다.

3. 호출 규칙

1) UI 컴포넌트

- 규칙1: 반드시 include()를 사용하며 상대경로를 사용한다.

if ($view_mode == VMODE_DETAIL) { include "_inc_view_detail.php"; }
elseif ($view_mode == VMODE_SIMPLE) { include "_inc_view_simple.php"; }

또는

if ($media_type == MTYPE_WMT) { include "player/_inc_player_wmt.php"; }
elseif ($media_type == MTYPE_REAL) { include "player/_include_player_real.php"; }

아마 _inc_player_wmt.php 를 호출하는 파일은 십중팔구 player.php 아니면 player_main.php 따위의 파일일겁니다. player_main.php 는 _inc_player_wmt.php를 불러 사용하지만 역으로 의존적이라고 할수도 있습니다.
player_main.php 의 위치가 옮겨지면 반드시 _inc_player_wmt.php 도 같이 옮겨져야 제대로 동작하는 거죠.
그러니까 두 파일을 최대한 가까이 위치시켜서 관리에 편하게 하자는 겁니다.

include()를 사용하는 이유는 치명적 오류를 발생시키지 않기 때문입니다.
UI 출력상의 문제가 치명적 오류이어야 할 경우도 있을수도 있겠습니다만..
극히 드물기 때문이죠..

2) 라이브러리

- 규칙1: 라이브러리 root 는 include_path 에 등록되어져야 한다. 그리고 라이브러리 root 에 대한 상대경로로 라이브러러 파일을 지정한다.

이렇게 하는 이유는 /a.php 와 /path/to/a.php 에서 동일한 코드로 사용하게 하기 위해서입니다.

어떤분이 반드시 상대경로로 써야한다고 했는데..
상대경로에만 의지할 경우
a.php 에서는 require_once("lib/_Some.php") 가 될 것이고
/path/to/a.php 에서는 require_once("../../lib/_Some.php") 가 될 것입니다.
이렇게 되면 관리가 불가능한것은 아니지만 매우 성가시게 됩니다.

만약 웹서버 설정에 권한이 없는 호스팅 환경이라면 다른 방법을 생각해야겠죠..

- 규칙2: 라이브러리 root는 웹서버 root 의 바깥에 위치해야한다.

include 되는 파일은 웹서버 root 바깥에 있어도 아무 상관없습니다.
오히려 보안성이 높아지는 결과가...

- 규칙3: 반드시 require_once()를 사용하며 다른 코드가 실행되기 전에 모두 위치시켜야한다.

<?
require_once "_BoardUtil.php";
require_once "DBLib/_DBLib.php";
...
?>

require_once()를 사용하는 이유는 해당 라이브러리를 사용할수 없을때 치명적 오류를 발생시키기 위해서입니다.
라이브러리중 하나라도 제대로 로드되지 않은 상태에서 처리가 진행될 경우 전체 데이터의 무결성에 문제가 생길수도 있기 때문이죠.

라이브러리 파일들은 HTML을 출력하거나 기타 처리코드를 포함하지 않기 때문에 페이지 최상단에서 require_once()되어도 다른 처리에 영향을 주지 않습니다.

4. 아직 확실하게 정리되지 않은 문제

라이브러리 간의 의존관계가 있는 경우,
예를 들어 _A.php 가 _B.php 의 실행에 반드시 필요한 경우

_B.php의 상단에 require_once "_A.php" 를 넣어줘야하느냐의 문제입니다.

넣어줄 경우 최종 처리 페이지에서 _B.php 만 require_once()해도 문제없이 실행됩니다.
그러나 전체적인 구조를 파악하는데 있어서 _A.php의 존재를 모르고 지나칠수도 있기 때문에 최종 페이지에는 require_once "_A.php" 도 같이 넣어주는 것이 바람직한것 같습니다.

아직 저는 이 문제를 확실하게 결정하지 못했는데. 일단은 라이브러리에도 넣고 최종 페이지에도 넣고 있는중입니다. (좋은 의견 있으면 말씀들 해주십쇼.)

이상입니다.


--------------------------------------------------------------
 이대규 너무 길어지는 것 같아서 서둘러 마무리를 했는데 한가지 팁을 빠트렸군요.. cron 에서 사용할 경우 다음과 같이 runphp.sh 라는 스크립트를 만들어서 php 파일을 실행하도록 하면 웹페이지와 배치작업을 똑같은 환경에서 사용할 수 있습니다.

#!/bin/sh
/www/bin/php -dinclude_path=/home/somesite.com/lib $*

0 * * * * /home/somesite.com/bin/runphp.sh /home/somesite.com/bin/batch_dosomething.php
 08/16 2:33:56 
 
 ☆~ 흠... 결론은 자신만의 특정한 규칙을 만들고 이글의 내용을 참조할것을 참조하여 쉽고 정확하게 관리하게 만들어라,,,, 인가보군요 @_@; 08/16 10:19:25 
 
 이종민 아~ 훌륭하게 작업하시는군요. 많은 참고가 되었습니다. 감사합니다. 08/16 12:38:18 
 
 1 간단하게 include파일이 스스로 실행되지 않게 함. 08/16 18:29:45 
 
 디토 파일 맨 위에 넣으면 include되는 파일이 스스로 실행되지 않게 합니다.
if (basename($_SERVER['PHP_SELF']) == basename(__FILE__)) exit;

관련자료

등록된 댓글이 없습니다.
Today's proverb
어진 이를 알아보는 것은 지(智)요, 어진 이를 추천하는 것은 인(仁)이며, 어진 이를 끌어들이는 것은 의(義)이다. (공자)