저는 까칠한 사람입니다. 주위의 많은 것에 불평합니다. 세상에는 제가 좋아하지 않는 기술들이 많이 있는데, 그것은 당연한 것입니다. 프로그래밍은 우스울 정도로 역사가 짧은 학문이고 우리 중 누구도 우리가 지금 무엇을 하고 있는지조차 갈피를 잡지 못합니다. 스터전의 법칙을 감안하면 주변에 불평할 것들이 평생에 걸쳐서 널려 있습니다.
모든 것이 같지는 않습니다. PHP는 그저 쓰기 어색하다거나 제가 원하는 부분과 맞지 않다거나 차선책이라거나 신념에 맞지 않는 것이 아닙니다. 저는 일반적으로 좋은 방식이라고 여겨지는 것 중에 제가 싫어하는 것들과 나쁜 방식이라고 여겨지는 것 중에 좋아하는 것들을 말씀드릴 수 있습니다. 자, 질문하세요! 이런 주제로 흥미로운 대화를 나눠 볼수도 있을 겁니다.
PHP는 유일한 예외입니다. 실질적으로 PHP의 모든 부분은 어딘가 잘못되어 있습니다. 언어, 프레임워크, 생태계 모두 그냥 개판입니다. 단 하나만 콕 집어서 얘기할 수도 없는 것이 아닙니다. 이 모든 폐해는 시스템 전반에 걸쳐 있기 때문입니다. 매번 PHP의 불만을 정리할 때마다 한번 쭉 훑은 상태에서도 자꾸만 트리비아가 질릴 정도로 발견되어서 이내 막혀버리고 맙니다. (그래서 프랙탈)
PHP는 나의 기술을 망치는 골칫거리입니다. PHP는 완전 개판이지만 아직 다른 것들을 배워보지도 않은 (권력이 있는) 아마추어들이 칭찬을 해대는 통에 미치겠습니다. 결점을 상쇄시킬만한 장점은 쥐꼬리만하고 저는 이 언어의 존재 자체를 잊어버리라고 권하고 싶습니다.
하지만 일단 모든 것을 정리해야 할 것 같군요. 자 갑니다, 이게 마지막이예요.
저는 막 Mel에게 저의 좌절감을 내뱉었고 그녀는 여기에 (그것에 대해) 써 보길 권했습니다.
심지어 저는 PHP의 뭐가 잘못되었는지조차 말할 수가 없어요. 왜냐 하면... 음.... 공구상자가 있다고 해 봐요. 각종 공구가 들어있는. 뭐 그런대로 괜찮아 보이겠죠. 표준적인 것들이 들어 있을거구요.
그런데 거기서 드라이버를 꺼냈는데 끝이 이상한 삼각형 모양인 거예요. 뭐 좋아, 나한테 유용하진 않겠지만 언젠간 쓸만할 때도 있겠지.
이제 망치를 꺼냈는데 경악스럽게도 양쪽 끝에 못뽑이가 달려 있네요. 뭐 어쨌든 쓸 수야 있겠죠. 옆으로 잡고 망치의 가운데 부분으로 못을 박는다거나 하는 식으로.
이제 펜치를 꺼냈는데 톱니 무늬가 없어요. 평평하고 매끈하네요. 유용하진 않지만 어쨌든 볼트를 돌릴 수야 있을테니 뭐 어때요.
그리고 계속 봅시다. 공구상자 안의 모든 것들이 괴상하지만 완전히 쓸모없진 않을 거예요. 그리고 전체적으로 보면 문제가 아니예요. 왜냐하면 일단 공구가 있긴 하니까.
이제 그 공구상자를 쓰면서 "이봐 대체 이 공구상자에 무슨 문제가 있다고? 난 지금까지 이것들을 써 왔고 이것들은 쓸만하다고!"라고 하는 말하는 수백만명의 목수들을 보게 됩니다. 그리고 목수들이 자기가 지은 집을 보여주는데, 방들은 죄다 오각형 모양이고 지붕은 거꾸로 뒤집혀 있군요. 그리고 정문을 두드리면 집이 폭삭 무너져 내리고 안에서 그 사람들이 왜 문을 부수냐고 소리를 지릅니다.
이게 PHP의 문제점이예요.
저는 아래의 요소들이 프로그래밍 언어를 생산적이고 가치있게 만드는 데 중요한 요소라고 주장하지만 PHP는 이 모든 것들을 심각하게 위반합니다. 만약 이것들이 중요하다는 것에 동의하지 않는다면 대체 어떤 것에 동의하실지 짐작할 수 없군요.
제 입장은 이렇습니다.
mysql_real_escape_string
, E_ACTUALLY_ALL
strpos
, str_rot13
===
==
, for ($foo as &$bar)
저는 매 문제 하나하나마다 왜 이것이 이 범주에 들어가는지 따로 부연설명을 하지 않을 겁니다. 한다면 아마 끝이 없겠지요. 아마 독자 여러분들이 스스로 생각하실 수 있을 거라고 믿습니다.
저는 PHP 논쟁을 많이 벌였습니다. 그럴 때마다 논의를 중단시키려는 목적 말고 없는 뻔한 반론들을 엄청나게 봐 왔습니다. 여기서 제발 저한테 이런 소리는 들이밀지 말아주세요. :(
여기서 사족, 저는 Python을 무척 좋아합니다. 원하신다면 저야 기쁘게 불평불만을 쏟아낼 수 있습니다. 저는 이게 완벽한 언어라고 말하지 않았습니다. 다만 저는 장점과 단점을 저울질해서 이것이 제가 쓰기에 가장 적합한 언어라는 결론을 내렸을 뿐입니다.
그리고 저는 PHP로 똑같은 걸 할 수 있는 개발자를 만나본 적이 없습니다. 하지만 이내 패배를 인정하고 사과하는 개발자들은 많이 봤습니다. 이런 사고방식은 대단합니다.
CPAN은 "Perl의 표준 라이브러리"로 불려 왔습니다. 그게 Perl의 표준 라이브러리에 대해서 많은 것을 이야기하는 건 아니지만 탄탄한 기반에서는 많은 대단한 것들을 만들어낼 수 있다는 점을 내포하고 있습니다.
각각 자료형에 대해서 연산자를 따로 두면 언어는 더 복잡해질 것입니다. 예를 들어 문자열형에 '==' 연산자를 쓰지 못하기 때문에 'eq' 같은 걸 쓴다고 칩시다. 그렇게 하는 의미를 잘 모르겠습니다. 특히나 PHP처럼 대부분의 스크립트가 단순하고 학습 곡선이 가파르지 않은 기본적인 문법으로 이루어진 언어를 원하는, 프로그래머가 아닌 사람들에 의해 만들어진 언어라면요.
(int)
는 C처럼 보이지만 int
는 존재하지 않습니다. 이름공간은 \
를 사용합니다. 새 배열 문법은 [key => value]
와 같이 해시 리터럴을 가진 언어들과는 다른 모양을 지니고 있습니다.멀리서 보면 수많은 경우가 있습니다. PHP 문서의 어딘가에서 가져온 이 코드를 보시죠.
@fopen('http://example.com/not-existing-file', 'r');
이 코드는 어떻게 동작할까요?
--disable-url-fopen-wrapper
로 컴파일되었다면 이 코드는 동작하지 않을 것입니다. (문서에서는 '동작하지 않는다'라는 것이 뭘 의미하는지 말하지 않습니다. 널을 반환하나? 예외를 발생시키나?) PHP 5.2.5에서 이 플래그가 사라졌다는 것을 유념하세요.allow_url_fopen
가 꺼져 있다면 역시 동작하지 않습니다. (어떻게? 몰라요.)@
때문에 존재하지 않는 파일이라는 경고 메시지는 출력되지 않을 것입니다.scream.enabled
가 켜져 있다면 출력됩니다.ini_set
로 scream.enabled
를 수동으로 설정했다면요.error_reporting
레벨이 설정되지 않았다면 출력되지 않을 겁니다.ini_set
를 통해 설정된 display_errors
에 달려 있을 겁니다.저는 이 별다른 해를 끼치지 않는 함수 호출이 PHP 컴파일 옵션과 서버 전역 설정, 그리고 프로그램 내부에서의 설정을 모르고서는 어떻게 동작할지 전혀 말씀드릴 수가 없습니다. 그리고 이 모든 것들은 전부 기본적으로 내장된동작입니다.
PHP는 전역적이고 암시적인 상태로 가득 차 있습니다. mbstring
는 전역 문자셋을 사용합니다. func_get_arg
와 그 친구들은 전형적인 함수처럼 보이지만 현재 실행중인 함수 위에서 동작합니다. 오류와 예외 처리에는 전역 기본값을 사용합니다. register_tick_function
함수는 매 틱(tick)마다 실행될 전역 함수를 설정합니다. 잠깐, 뭐라구요?!
fork
가 없는 문제와 더불어 (나중에 언급합니다) 이것은 PHP를 통해 병렬 프로그램을 작성하는 것을 극히 어렵게 만듭니다.PHP 코드 중 일부는 버그를 발생시키도록 설계되었습니다.
json_decode
함수는 잘못된 입력이 들어오면 null을 반환합니다. 하지만 문제가 전혀 없는 JSON 객체일지라도 null을 반환할 수 있습니다. 이 함수는 매번 사용할 때마다 json_last_error
를 같이 호출하지 않는 한전혀 신뢰할 수 없습니다.array_search
, strpos
함수는 문자열의 처음 위치에서 찾으면 0
을 반환합니다. 하지만 찾지 못한다면 false를 반환합니다.두번째 경우를 조금 더 설명드리도록 하겠습니다.
C 언어에서는 strpos
같은 함수는 서브스트링을 찾지 못하면 -1
을 반환합니다. 만약 그런 경우를 체크하지 않는다면 당신은 엉뚱한 메모리 영역을 가리키게 될 것이고 프로그램은 터질 것입니다. (아니, 아마도요. 이건 C 언어잖아요. 씨발 대체 누가 알겠냐고. 하지만 최소한 이런 경우를 위한 도구는 있죠.)
이를테면 파이썬에서 비슷한 역할을 하는 .index
메소드는 찾지 못했을 경우 예외를 발생시킵니다. 역시 그런 경우를 체크하지 않으면 프로그램은 터지게 되겠죠.
PHP에서 이러한 함수들은 false를 반환합니다. 만약 FALSE
를 배열 인덱스값으로 사용하거나 ===
로 비교하지 않고 이런 저런 짓을 할 경우에 PHP는 false 값을 0
으로 알아서 변환해 버립니다. 그럼 당신의 프로그램은 터지지 않겠죠. 대신 당신의 프로그램은 strpos
함수를 사용할 때마다 결과값을 일일히 체크하는 보일러플레이트 코드를 넣지 않는 한 일말의 경고도 없이 엉뚱한 짓을 하게 됩니다.
이건 정말 나쁜 겁니다! 프로그래밍 언어는 나와 같이 일을 하기 위해서 만들어진 도구잖아요. 여기, PHP는 개발자가 빠지라고 스스로 찾기 힘든 곳에다가 함정을 파 버립니다. 그리고 저는 문자열 처리나 결과값이 같은지 비교하는 등의 따분한 작업을 하면서도 신경을 곤두세워야 합니다. PHP는 그야말로 지뢰밭입니다.
저는 PHP 인터프리터나 그것의 개발자들에 대해 수많은 멋진 이야기를 들었습니다. 그런 이야기들은 PHP 코어나 디버깅된 PHP 코어, 코어 개발자와 소통해본 사람들에게서 왔습니다. 하지만 그 중 칭찬의 말은 단 하나도 없었습니다.
저는 그래서 여기서 결론을 내립니다. 왜냐 하면 자꾸 반복되는 얘기라서요. PHP는 아마추어들의 커뮤니티입니다. PHP를 디자인하고 작업하고 코드를 짜는 사람들 중에 자기가 뭘 하는지 제대로 아는 것 같은 사람들은 극히 드뭅니다. (아 이것 참, 독자 여러분들은 물론 드문 예외죠!) 그리고 문제의 실마리를 잡을 수 있는 사람들은 다른 플랫폼으로 가 버리게 됨으로서 결국 전체에 있어서 평균적인 능력은 계속 줄어들게 됩니다. 바로 이것이 PHP가 가지고 있는 가장 큰 문제점입니다. 그야말로 장님이 장님의 무리를 이끌고 있는 상황.
좋아요, 일단 팩트로 돌아갑시다.
==
는 쓸모 없습니다.
"foo" == TRUE
, "foo" == 0
… 하지만 물론 TRUE != 0
.==
연산자는 가능하다면 숫자, 그러니까 부동 소숫점 자료형으로 변환합니다. 그러므로 긴 16진수 문자열을 비교할 때 (이를테면 비밀번호 해쉬 값이라거나) 그렇지 않은 경우에도 참으로 비교해 버립니다."6" == " 6"
, "4.2" == "4.20"
, "133" == "0133"
입니다. 하지만 133 != 0133
이라는 건 명심하세요. 왜냐 하면 0133은 8진수 표기죠.===
연산자는 값과 자료형을 같이 비교합니다… 객체만 제외하고요. ===
는 실제로 같은 객체일 경우에만 참이 됩니다. 객체에 대해서만 ==
연산자는 ===
연산자가 다른 자료형에 하는 것처럼 값(과 그 안의 모든 어트리뷰트)과 자료형을 같이 비교합니다. 뭐?NULL < -1
이 참이면서 NULL == 0
도 참입니다. 따라서 정렬도 규칙적이지가 못합니다. 정렬 알고리즘이 배열 원소들을 어떻게 비교하냐에 따라 달라집니다.==
은 ===
가 있습니다. 타입 안전한 <
는… 없네요. 당신이 무엇을 하든간에 무조건"123" < "0124"
입니다.+
연산자를 오버로딩하지 않습니다. +
는 무조건 더하기 연산이고 문자열 결합은 무조건 .
입니다.[]
배열 첨자 연산자는 {}
로 쓸 수도 있습니다.[]
는 문자열과 배열 뿐만 아니라 어떤 변수에도 쓸 수 있습니다. 널을 반환하고 어떤 경고도 발생하지 않습니다.[]
로 배열을 자를 수 없습니다. 오직 하나의 원소만 가져올 수 있습니다.foo()[0]
는 문법 오류입니다. (PHP 5.4에서 고쳐짐)(말 그대로) 어떤 다른 비슷한 문법을 가진 언어들과 달리, ?:
는 left-associative합니다. 예를 들어
$arg = 'T';
$vehicle = ( ( $arg == 'B' ) ? 'bus' :
( $arg == 'A' ) ? 'airplane' :
( $arg == 'T' ) ? 'train' :
( $arg == 'C' ) ? 'car' :
( $arg == 'H' ) ? 'horse' : 'feet' );
echo $vehicle;
는 horse
를 출력합니다.
global
선언이 필요합니다. 이건 위의 원인에 따른 자연스러운 결과이기 때문에 타당할 수도 있습니다. 다만 명시적인 선언 없이는 전역 변수를 읽을 수조차 없다는 점을 제외하면요. PHP는 대신 같은 이름의 지역 변수를 조용히 만듭니다. 제가 아는 것 중에 비슷한 스코핑 문제를 가지고 있는 다른 언어는 없습니다.$x = new SplBool(true); $x = "foo";
는 동작하지 않을 것입니다. 이건 보시다시피 정적 타이핑과 비슷한 것입니다.array()
와 기타 유사한 것들은 함수가 아닙니다. array
그 자체는 아무 역할도 하지 않습니다.$func = "array"; $func();
는 동작하지 않습니다.list($a, $b) = ...
로 할 수 있습니다. list()
역시 array
처럼 함수 비스무리한 문법입니다. 왜 이것이 별개의 문법으로 구현되지 않고 혼란스럽게 되어 있는지는 모릅니다.(int)
는 분명히 C 언어처럼 보이기 위해 만들어졌습니다. 하지만 저것 자체가 하나의 토큰입니다. PHP에는int
라는 것이 없습니다. 한번 직접 해 보세요. var_dump(int)
는 동작하지 않을 뿐만 아니라 (int)
자체를 캐스팅 연산자로 해석해서 파싱 오류를 뱉어냅니다.(integer)
는 (int)
와 같습니다. (bool)
/(boolean)
나 (float)
/(double)
/(real)
같은 것도 있습니다.(array)
와 객체를 위한 (object)
연산자도 있습니다. 말도 안 되는 것처럼 보이겠지만 쓸 만한 구석이 있긴 있습니다. 만약 함수의 인자로 한 개의 아이템 또는 리스트를 넘기고 싶고 둘 다 똑같이 취급하고 싶을 때 쓸 수 있습니다. 하지만 그다지 신뢰성이 없는 것이, 만약 한개의 객체를 넘긴다면 그걸 배열로 캐스팅하면 배열 하나가 만들어져서 그 객체의 어트리뷰트를 담게 됩니다. (만약 객체로 캐스팅하면 그 반대의 과정이 일어납니다.)include()
와 그 친구들은 기본적으로 C의 #include
와 같습니다. 다른 소스 파일을 읽어서 그 안에 그대로 때려박습니다. PHP에는 모듈 시스템이 없습니다. 심지어 PHP 코드에도요.$foo[] = bar;
처럼 합니다.echo
는 함수가 아니라 선언문 같은 겁니다.empty($var)
는 함수같이 생겼지만 함수가 아닌 것의 극단적인 예일 것입니다. 예를 들어empty($var || $var2)는
파싱 오류를 냅니다. 대체 세상에 왜 파서가 empty라는 것의 존재에 대해서 알고 있어야 하는 겁니까?if (...): ... endif;
등등...@
입니다. (실제로는 DOS에서 가져옴)::
연산자를 T_PAAMAYIM_NEKUDOTAYIM
, <<
연산자를 T_SL로
표현합니다. 제가 "내부적"이라고 적었지만 만약에 저것들을 엉뚱한 곳에 집어넣었다가는 에러 메시지에서 저 문구를 보게 될 겁니다.E_STRICT
가 있지만 실제로 도움이 되는 것처럼 보이지는 않습니다. 게다가 이것이 실제로 무슨 역할을 하는지에 대한 문서도 없습니다.E_ALL
는 모든 에러 범주를 포함합니다. E_STRICT
만 제외하고요. (5.4에서 수정됨)무엇이 허용되고 무엇이 허용되지 않는지에 대한 일관성이 전혀 없습니다. E_STRICT
가 여기서 어떻게 작용하는지는 잘 모르겠지만 이것들은 허용됩니다.
$foo->x
. (경고)2 < "foo"
(출력되지 않음)foreach (2 as $foo);
(경고)그리고 이것들은 허용되지 않습니다.
$foo::x
(치명적 오류)foo()[0]
(파싱 오류. 위에서 언급했다시피 PHP 5.4에서 해결되었습니다)그리고 여기서 언급한 것이 아니더라도 기타 이상한 파싱 에러가 있습니다.
__toString
메소드는 예외를 발생시키지 않습니다. 만약 발생시키려고 하면 PHP는 음... 어... 예외를 발생시키긴 합니다. (실제로는 치명적 오류입니다. 그런대로 괜찮긴 합니다만....)
PHP 에러(PHP error)와 PHP 예외(PHP exception)는 완전히 다릅니다. 이 둘은 전혀 상호작용하지 않습니다.
trigger_error
를 호출하는)는 try
/catch
로 잡을 수 없습니다.set_error_handler
로 설치된 오류 핸들러를 타지 않습니다.set_exception_handler
가 따로 있습니다. 왜냐하면 프로그램의 진입점부터 try
블록으로 감싸는 것은 mod_php
모델에서는 불가능하기 때문입니다.new ClassDoesntExist()
)는 어떤 것에도 잡히지 않습니다. 수많은 비교적 무해한 문제들도 치명적인 오류를 발생시키며 의문스러운 이유로 프로그램을 종료시켜 버립니다. 종료 함수는 계속 돌지만 스택 추적을 할 수가 없으며 (최상단에서 돌기 때문) 프로그램이 제대로 종료되었는지 오류로 인해 종료된 건지 쉽게 알 수가 없습니다.Exception
이 아닌 객체를 throw
하면.. Fatal Error가 납니다. Exception이 아니라요..finally
구문이 없습니다. 따라서 래퍼 코드(핸들러 설정 - 코드 실행 - 핸들러 해제나 몽키패칭 - 테스트 수행 - 몽키패치 해제)를 작성하기가 귀찮고 어렵습니다. 객체지향이나 예외처리의 많은 부분을 Java에서 가져왔음에도 불구하고 이것은 의도적입니다. 왜냐 하면 "PHP의 문맥에서는 별로 말이 안 된다."라나요. 엉? (5.5에서 고쳐짐)
함수 인자들은 실제로는 정적 타이핑과 다름없는 "형 힌트"라는 것을 가지고 있습니다. 모든 내장 함수들이 이런 방식의 타이핑을 사용하는데도 형 힌트로 int
, string
, object
나 기타 "핵심" 자료형을 줄 수가 없습니다. 아마도 PHP에 int
같은 것이 것이 없기 때문일 겁니다. (앞에서 이야기한 (int)
를 보세요.) 내장 함수에서 엄청나게 쓰이는 mixed
, number
, callback
같은 유사 형들도 마찬가지입니다. (callable
은 5.4에서 허용되게 수정되었습니다)
function foo(string $s) {}
foo("hello world");
는 이런 에러를 발생합니다.
PHP Catchable fatal error: Argument 1 passed to foo() must be an instance of string, string given, called in...
string
같은 클래스는 없죠. 만약 ReflectionParameter::getClass()
로 형 힌트를 동적으로 검사하려고 하면 그런 클래스는 존재하지 않는다고 할 겁니다. 따라서 실제 클래스 명을 받아오는 것은 불가능하게 되죠.현재 함수의 인자를 다른 함수로 넘기는(디스패치, 그렇게 보기 드문 것은 아닙니다) 것은call_user_func_array('other_function', func_get_args())
를 통해 할 수 있습니다. 하지만 func_get_args
함수는 실행 중에 이것이 함수 파라미터가 될 수 없다면서 치명적인 오류를 뱉습니다. 이게 대체 무슨 에러랍니까? (PHP 5.3에서 고쳐짐)
&
를 쓰지 않으면요.func_num_args
, func_get_arg
, func_get_args
같은 함수를 쓰면서 법석을 떨어야 합니다. 그런 것을 위한 문법은 존재하지 않습니다.PHP의 함수와 관련된 부분은 C와 비슷하게 설계되었고 객체(ㅋㅋㅋ)와 관련된 부분은 자바와 비슷하게 설계되었습니다. 저는 이것이 얼마나 거슬리는지 이루 말할 수 없습니다. 클래스 체계는 좀 더 저수준의 Java 언어를 본따서 설계되었고 동시대의 다른 언어들에 비해서 자연스럽고 의도적으로 더욱 제한되어 있습니다. 저는 당황했습니다.
getFoo
같은 접근자를 사용합니다.__get
같은 것들을 가지고 있습니다. (PHP 문서에서는 불가사의하게도 그러한 특수한 메서드들을 "overloading"이라고 부릅니다.)var
이나 const
)과 같은 느낌이 들지만, PHP의 다른 절차적인 부분에서는 그렇지가 않습니다.foreach ($obj as $key => $value)
는 그냥 객체의 모든 접근 가능한 어트리뷰트를 순회하는 것 뿐입니다.클래스는 객체가 아닙니다. 메타프로그래밍을 위해선 마치 함수처럼 문자열로 된 이름을 통해서 참조해야 합니다.
instanceof
는 연산자입니다. 자바의 영향일까요? 클래스가 일급객체가 아니다? (저는 잘 모르겠군요.)
is_a
함수는 있습니다. 그 객체가 주어진 클래스 이름 문자열과 같은지 확인하기 위한 추가적인 인자와 함께요.is_subclass_of
와 마찬가지로 get_class
는 함수입니다. typeof
는 없습니다.int
같은 건 애시당초 없는 겁니다) 이런 경우를 위해, is_int
같은 것들이 필요해집니다.clone
이 연산자라고?!$obj->$foo
이지만 클래스 어트리뷰터는 $obj::foo
. 이런 짓을 하는 다른 언어에 대해서는 들어본 적이 있고 이게 얼마나 유용할지도 모르겠습니다.Class::method()
)으로 호출할 수 있습니다. 만약 다른 메소드에서 호출된다면 현재 $this
를 가진 일반적인 메소드 호출이 되어야 한다고 생각합니다.new
, private
, public
, protected
, static
등등등... Java 개발자들을 이기기 위한 겁니까? 개인적인 취향이라는 건 알고 있지만 왜 이런 것들이 동적 언어에서 필요한 건지 잘 모르겠습니다. C++에서 이런 것들은 대부분 컴파일이나 컴파일 타임에서 이름을 찾는 것에 관련되어 있잖아요.list()
는 특수한 문법이라 (함수가 아니라) 파서가 혼란스러워 하기 때문입니다. 이게 모호할 이유가 전혀 없는데, 클래스에 몽키패칭은 잘 됩니다. ($foo->list()
는 구문 오류가 아닙니다.)new Foo(bar())
에서 bar()
가 예외를 생성할 경우) 생성자가 호출되지 않고 소멸자가 호출됩니다. (PHP 5.3에서 고쳐짐)__autoload
나 소멸자 안에서 발생한 예외는 치명적 오류를 발생시킵니다.__construct
는 Python의 __init__
처럼 초기화 메소드(initializer)입니다. 클래스에 호출하여 메모리를 할당하고 객체를 생성하는 방법은 없습니다.__construct
를 정의하고 있지 않은 경우에parent::__construct()
를 호출하면 치명적 오류가 발생합니다.for...as
)이 알아들을 수 있는 형태의 반복자 인터페이스를 제공하는데 어떤 내장 형식 (예를 들면 배열)도 이를 구현하고 있지 않습니다. 만약 배열의 반복자를 원한다면ArrayIterator
로 감싸야 합니다. 잇거나 나누거나 반복자를 일급객체로 사용하는 기본적 방법은 없습니다.__toString
가 정의되어 있지 않다면 내장된 혹은 사용자 정의된 객체(심지어 클로져까지)를 문자열로 변환할 시 오류가 발생하게 됩니다. 심지어 echo
까지 잠재적으로 오류를 낼 가능성을 가지고 있습니다.예를 들어 Perl은 "약간의 조립 과정이 필요"입니다. Python은 "배터리 포함"입니다. PHP는 캐나다산 키친 싱크대입니다. 게다가 양쪽 수도꼭지에 전부 C라고 적혀 있습니다.
라이브러리 덩어리들은 전혀 일관성을 갖추고 있지 않습니다.
strpos
/ str_rot13
, php_uname
/ phpversion
, base64_encode
/ urlencode
,gettype
/ get_class
ascii2ebcdic
, bin2hex
, deg2rad
, strtolower
, strtotime
base64_encode
, str_shuffle
, var_dump
vs create_function
,recode_string
array_filter($input, $callback)
vs array_map($callbasck, $input)
,strpos($haystack, $needle)
vs array_search($needle, $haystack)
usleep
vs microtime
i
가 붙냐 안 붙냐에 따라 달라집니다.array_
로 시작하지만 나머지는 그렇지 않습니다.키친 싱크대. 라이브러리는 이런 것들로 구성되어 있습니다.
DOM
(OO), DOM XML
(아님), libxml
, SimpleXML
, XML Parser
,XMLReader
/XMLWriter
, 그리고 기억할 수 없는 수많은 약어들. 분명히 저것들 사이에 차이점은 있을거고 어떤게 어떤건지 알아보는 것은 자유겠죠.SPPLUS
와 MCVE
. 뭐라고?mysql
, mysqli
그리고 PDO
추상화 뭐시기.이 내용은 아주 중요합니다. 왜냐하면 전혀 말도 안되는 것들인데도 언어 전반에 퍼져있으니까요. PHP는 하이레벨, 동적타이핑 프로그래밍 언어라구요. 그런데도 스탠다드 라이브러리의 대다수가 C API를 아주 살짝 싸놓았을 뿐입니다. 그 결과 아래와 같은 일이 생기죠.
mysql_real_escape_string
같은 함수들. 심지어 고장난 mysql_escape_string
와도 같은 인자를 갖는데, 이는 단지 MySQL C API의 일부이기 떄문입니다.dba_firstkey
없이 dba_nextkey
를 호출하면 segfault로 죽습니다.fopen(directory, "r")
는 리눅스에서는 잘 돌지만 윈도우에서는 false를 반환하고 워닝을 발생시킵니다.ctype_*
함수들(예를 들면 ctype_alnum
)이 있습니다.그런거 없습니다. 만약 두가지의 미묘하게 다른 기능이 필요하다면 PHP에는 두 개의 함수가 있습니다.
배열을 어떻게 반대 방향으로 정렬하나요? Perl이라면 sort { $b <=> $a }
로 하면 되겠죠. Python은?.sort(reverse=True)
로. PHP에서는 rsort()
라는 별개의 함수가 있습니다.
curl_error
, json_last_error
, openssl_error_string
,imap_errors
, mysql_error
, xml_get_error_code
, bzerror
, date_get_last_errors
. 더 있나요?array_multisort
, arsort
, asort
, ksort
, krsort
, natsort
, natcasesort
, sort
,uasort
, uksort
, usort
ereg
, eregi
, mb_ereg
, mb_eregi
, preg_match
, strstr
, strchr
, strichr
,strrchr
, strpos
, stripos
, strrpos
, strripos
, mb_strpos
, mb_strrpos
+ 기타 등등 바리에이션strstr
/ strchr
, is_int
/ is_integer
/ is_long
,is_float
/ is_double
, pos
/ current
, sizeof
/ count
, chop
/ rtrim
, implode
/ join
, die
/exit
, trigger_error
/ user_error
…scandir
는 주어진 디렉토리의 파일 목록을 반환합니다. 디렉토리의 순서대로 파일 목록을 반환하는 것이 아니라 파일을 알파벳 순서로 정렬해서 반환합니다. 그리고 역순으로 정렬하기 위해 추가적인 인자를 받습니다. 아마 정렬 함수로는 충분하지 않았나 봅니다.str_split
함수는 문자열을 일정한 길이의 덩어리들로 나눕니다. chunk_split
함수는 문자열을 일정한 길이의 덩어리로 나누고 구분자로 합칩니다.call_user_func_array
), printf
/vprintf
나 sprintf
/ vsprintf
같이 나누는 경우가 있습니다. 전자는 인자들을 받고 후자는 인자들로 이루어진 배열을 받습니다.preg_replace
에 /e
(eval) 플래그를 걸면 문자열을 정규식 규칙에 맞게 변환하고, 그것을 평가(eval)합니다.strtok
는 해당하는 C 함수와 똑같이 만들어졌습니다. 이것은 여러 의미에서 나쁜 아이디어인데, PHP로는 배열을 쉽게 반환할 수 있다는 점을 명심하세요. (C에서는 그게 어렵죠) 그리고 strtok(3)
이 쓰는 핵(문자열 내용을 그 자리에서 고치기)은 여기서 사용되지 않습니다.parse_str
는 쿼리 문자열을 파싱합니다. 이름만 가지고는 그걸 전혀 알 수 없죠. 그리고 이것은 따로 배열 인자를 주지 않으면 register_globals
처럼 동작하여 쿼리 내용을 전역 범위에 변수로 넣어버립니다. (물론 아무것도 반환하지 않습니다.)explode
함수에 빈 구분자를 줄 수 없습니다. 다른 대부분의 문자열 나누기 함수는 구분자를 주지 않으면 문자 단위로 나눈다는 것으로 간주합니다. 대신 PHP에선 완전히 다른 함수가 이 역할을 하는데, 이름도 혼란스럽게str_split
입니다. 그리고 "문자열을 배열로 변환하는데 사용된다"고 설명되어 있습니다.strftime
라는 함수가 있습니다. 완전히 다른 사용법의 date
라는 함수도 있는데 영어로만 동작합니다.gzgetss
— gz 파일 포인터에서 한 줄을 읽어서 HTML 태그를 벗겨냅니다.” 저는 이런 함수가 존재하는 의미가 뭔지 궁금해 미칠 것 같습니다.mbstring
ereg_*
함수를 제공하지만 더 이상 지원하지 않습니다(deprecated). 전용 플래그를 걸어줘야 UTF-8을 인식할 수 있긴 하지만 preg_*
는 운이 없는 편이죠.compact
와 extract
는 빙산의 일각일 뿐이죠.classkit
로 사용자가 정의한 클래스를 바꿀 수 있습니다. runkit
로 그것을 대체하고 사용자가 정의한 모든 것을 바꿀 수 있습니다. Reflection*
클래스로 언어의 거의 모든 부분을 반영할 수 있습니다. 하지만 이미 함수나 클래스의 프로퍼티에 접근하기 위한 수많은 개별 함수들이 있습니다. 이것들은 서브시스템에 독립적일까요, 관계가 있을까요, 아니면 단지 잉여일 뿐일까요?get_class($obj)
는 객체의 클래스명을 반환합니다. get_class()
는 현재 호출되는 함수의 클래스명을 반환합니다. 하나의 함수가 두개의 완전히 다른 역할을 한다는 점은 차치하구요, 그럼 get_class(null)
은? 후자의 역할을 합니다. 그러니 임의의 값을 믿으면 안 됩니다. 놀라셨죠?stream_*
클래스로 파일스러운 내장 객체들과 비슷한 스트림 객체를 구현할 수 있습니다. 하지만 "tell"은 내부적인 문제 때문에 구현할 수 없습니다. (게다가 이 시스템에 연계된 함수들은 엄청나게 많습니다.)register_tick_function
는 클로져를 받아들일 수 있습니다. unregister_tick_function
는 받아들이지 못하는데, 대신 클로져 객체를 문자열로 변환할 수 없다는 에러를 발생할 겁니다.php_uname
함수는 현재 OS에 대해 출력합니다. PHP가 현재 작동중인 OS를 알 수 없으면 PHP를 컴파일한 시스템의 OS 정보를 알려 줍니다. 그리고 그 두가지 상황을 구분할 수 있는 방법은 없습니다.fork
와 exec
는 내장 함수가 아닙니다. 이것들은 pcntl
확장에 들어 있으며 기본적으로 포함되어 있지 않습니다. popen
는 pid를 제공하지 않습니다.stat
의 반환값이 캐시됩니다.session_decode
는 임의의 PHP 세션 문자열을 읽을 때 사용합니다. 하지만 이미 활동 중인 세션이 있을 때만 동작합니다. 그리고 결과값을 반환하는 것이 아니라 $_SESSION
에다가 때려박습니다.curl_multi_exec
는 실행 중 오류가 발생하면 curl_errno
를 바꾸지 않습니다. 하지만 curl_error
는 건드립니다.mktime
의 인자 순서는 이렇습니다: 시간, 분, 초, 월, 일, 년.프로그램은 데이터를 먹고 더 많은 데이터를 뱉어내는 것 이상도 아닙니다. awk부터 시작해서 Prolog, C까지 좋은 언어들은 조작할 데이터의 유형에 따라 설계됩니다. 만약 언어가 데이터를 만질 수 없다면 아무것도 할 수 없다는 겁니다.
012
는 숫자 10
이 되겠지요. 하지만 08
는 숫자 0
이 됩니다. 8
이나 9
, 그리고 그 뒤에 오는 숫자는 전부 사라집니다. 01c
는 문법 오류입니다.pi
는 함수입니다. M_PI
는 상수입니다.0x0+2
의 결과는 4
입니다. 파서는 2
를 16진수 리터럴과 별도의 10진수 리터럴 양쪽 전부로 처리합니다. 즉,0x002 + 2
로 처리한다는 뜻입니다. 0x0+0x2
역시 비슷한 문제를 발생합니다. 그런데 이상하게도 0x0 +2
역시4
이지만, 0x0+ 2
는 2입니다. (5.4에서 고쳐졌지만, 같은 버전에서 0b
리터럴에 대해서 다시 문제가 발생했습니다. 0b0+1
가 2
가 되는 걸로.)pow
함수만 있습니다.mbstring
확장이 있는데 엉망입니다.é
와 É
를 비교하지 못합니다."$foo['key']"
는 문법 오류를 냅니다. 따옴표를 없애거나 (그럴 경우 경고문이 뜹니다!) ${...}
/{$...}
를 쓸 수밖에 없습니다."${foo[0]}"
는 괜찮아요. "${foo[0][0]}"
는 문법 오류를 냅니다. $
를 안쪽에 넣으면 둘 다 문제가 없습니다. Perl 문법의 열화 카피인 걸까요? (거기다가 의미는 완전히 다른)Oh, man.
=>
는 연산자가 아닙니다. array(...)
나 foreach
안에서만 존재하는 특별한 문법입니다.-1
는 0
처럼 유효한 키값입니다.array(...)
가 단축 표현이죠. (PHP 5.4에서 "리터럴"이 생겼습니다. [...]
)=>
문법은 Perl에서 따 온 것입니다. Perl에서는 따옴표 없이 foo => 1
처럼 쓸 수 있게 해 줍니다. (사실 이렇게 따옴표를 생략할 수 있게 해 주는 게 Perl에서 =>
연산자가 존재하는 이유입니다. 이 차이 외에는 그냥 콤마와 같습니다) PHP에서는 경고를 발생시키지 않고 이렇게 하는 것이 불가능합니다. 문자열 키에 따옴표를 쓰지 않고 해시를 만드는 걸 이런 식으로 할 수 있는 언어는 없습니다.배열 함수들은 배열과 해시, 이 둘이 섞인 경우 혼란스럽고 일관성 없는 동작을 합니다. 예를 들어 "배열의 차이를 계산하는" array_diff
함수를 보시죠.
$first = array("foo" => 123, "bar" => 456);
$second = array("foo" => 456, "bar" => 123);
echo var_dump(array_diff($first, $second));
이 코드는 어떻게 동작할까요? 만약 array_diff
함수가 인자를 해시로 인식한다면 분명히 이것들은 같은 키에 다른 값을 가지고 있으니 다르겠지요. 만약 리스트로 인식한다면 다를 겁니다. 배열의 순서가 다르거든요.
사실 array_diff
함수는 이 둘을 같은 것으로 봅니다. 왜냐 하면 이것을 집합으로 인식하거든요. 값만 비교하고 순서는 무시해 버립니다.
비슷한 맥락에서 array_rand
는 배열의 랜덤한 키값을 선택하는 이상한 동작을 합니다. 목록에서 뭔가를 선택할 일이 있을 때의 대부분의 공통적인 케이스에서 도움이 안 되죠.
PHP 코드가 얼마나 키값의 순서를 보존하는데 의존하는지에도 불구하고
array("foo", "bar") != array("bar", "foo")
array("foo" => 1, "bar" => 2) == array("bar" => 2, "foo" => 1)
만약 배열이 섞여 있으면 어떤 일이 벌어지는지 파악하는 것은 독자들에게 맡깁니다. (전 몰라요.)
array_fill
함수는 길이가 0인 배열을 만들 수 없습니다. 대신 경고를 발생시키고 false를 반환합니다.array_reverse
함수는 새 배열을 반환합니다.ArrayObject
클래스가 있습니다. 사용자 클래스는 같은 인터페이스를 구현할 수 있습니다. 하지만 몇 개의 메소드만 있을 뿐이고 절반 가까이는 내장 배열 함수처럼 보이지 않으며 배열을 받는 내장 함수는 ArrayObject
나 다른 배열과 비슷한 클래스를 어떻게 다룰 지 모릅니다.var_dump(strstr)
는 리터럴 문자열 "strstr"
로 예상하고 경고를 출력합니다. 임의의 문자열과 함수 "참조"를 알아차릴 방법은 없습니다.create_function
은 기본적으로 eval
의 래퍼입니다. 이것은 정규 이름을 가진 함수를 만들어서 전역에 설치합니다. (그래서 가비지 컬렉팅이 되지 않습니다. 루프 안에서 쓰지 마세요!) 이 함수는 현재 범위에 대해서 전혀 모르기 때문에 클로져가 아닙니다. 이름에는 NUL
바이트가 들어가 있기 때문에 일반 함수와 충돌하지 않습니다. (왜냐하면 파일의 어딘가에 NUL
이 들어가 있으면 PHP 파서가 실패해버리기 때문입니다)__lambda_func
라는 함수를 선언하면 create_function
가 동작하지 않습니다. 실제 구현은 __lambda_func
라는 함수를 만들어서 내부적으로 깨진 이름을 바꾸는 eval
인데, __lambda_func
라는 이름이 이미 있으면 처음 단계에서 치명적 오류를 뱉게 되는 것입니다.NULL
을 증가(++
)시키면 1
이 됩니다. 감소(--
)시키면 NULL
이 됩니다. 비슷하게 문자열을 감소시키면 아무 것도 변하지 않습니다.php.ini
파일 하나가 PHP의 엄청나게 많은 기능들을 조종하고 무엇을 언제 오버라이드할지를 결정하는 복잡한 규칙을 도입합니다. 보통 임의의 장치에 설치되는 PHP 소프트웨어는 환경에 맞추도록 설정을 오버라이드해야 하기 때문에 php.ini
같은 메커니즘을 사용하는 것은 이해할 수 없습니다.
php.ini
파일을 여러 곳에서 읽기 때문에, (그렇지 않을 수도 있지만) 기본 파일을 덮어 씌울 수도 있습니다. 하지만 실제로 읽는 것은 파일 하나 뿐이기 때문에, 원하는 세팅만 덮어 씌우는 것은 불가능합니다.PHP는 기본적으로 CGI 형식으로 동작합니다. 매번 페이지를 읽을 때마다 PHP는 실행하기 전에 모든 코드를 재컴파일합니다. 심지어 Python의 장난감 프레임워크의 개발 서버도 이런 식으로 돌지는 않습니다.
이로 인해 한번 컴파일하고 PHP 코드를 어떤 다른 언어만큼 빠르게 가속시킨다는 "PHP 가속기" 시장이 생겨났습니다. PHP의 배후에 있는 회사인 Zend는 이것을 자신들의 수익 모델로 포함시켰습니다.
한동안 PHP 오류는 기본적으로 클라이언트쪽으로 뿌려졌습니다. 아마 개발 중에 도움이 되라고 그렇게 했을 겁니다. 지금은 더이상 그렇지 않은 것 같지만 여전히 가끔씩 페이지의 상단에 mysql 오류를 볼 때가 있습니다.
mod_rewrite
를 쓰던 FastCGI를 쓰던 리버스 프록싱을 하던 Server:
설정을 바꾸던 관계 없습니다.<?php … ?>
태그 바깥의 공백 (심지어 라이브러리까지)은 리터럴 텍스트로 간주되어 응답에 섞여집니다. (그리고 "헤더가 이미 보내짐" 오류를 일으키기도 합니다) 유명한 해결 방법으로는 ?>
토큰을 버리는 겁니다. PHP는 불평하지 않을 것이고 뒤에 줄넘김이 붙지도 않을 것입니다.배치는 주로 PHP의 가장 큰 장점 중 하나로 꼽혀 왔습니다. 그냥 파일만 몇개 놓으면 끝이라는 거죠. 정말로 Python이나 Ruby, Perl보다 모든 과정이 쉽긴 하지만 미비한 점이 많습니다.
전반에 걸쳐서 저는 웹 어플리케이션을 앱 서버로 돌리고 리버스 프록싱을 하는 것을 선호합니다. 설정하는데 그렇게 노력이 들지도 않고 장점은 충분합니다. 웹 서버와 앱을 따로 관리할 수 있으며 웹 서버를 더 설치할 필요 없이 여러 개의 앱 프로세스를 동시에 돌릴 수 있으며 다른 사용자로 앱을 돌리는 것도 쉬우며 웹 서버를 교체할 수도 있으며 웹 서버를 건드리지 않고 앱을 내릴 수도 있으며 FIFO 시점만 조정하는 것으로 빈틈없이 배치할 수 있습니다. 웹 어플리케이션을 웹 서버와 합치는 것은 불합리하며 그렇게 해서 얻을 수 있는 이점은 없습니다.
php.ini
는 어디에서나 동작하는 모든 PHP 어플리케이션에 적용됩니다. 단지 하나의 php.ini
파일이 있을 뿐이고 전역적으로 적용됩니다. 만약 공유된 서버에서 설정을 고쳐야 하거나 두개의 어플리케이션을 돌리는데 둘이 서로 다른 설정이 필요하다면? 당신은 운이 없는 겁니다. 만약 할 수 있다면 모든 필요한 설정들의 합집합을 만들고 앱 내부에서 ini_set
를 사용하거나 .htaccess
파일을 체크하는 식으로 줄여가야 합니다. 그리고 와, 설정이 어떻게 값을 읽어오는지 확인하기 위해 체크해야 할 엄청나게 많은 곳들이 있네요..svn
) 역시 보호가 필요합니다. mod_php
에서는 파일시스템의 모든 것들이 잠재적인 진입점이 됩니다. 앱 서버에서는 단 하나의 진입점과 어떤 URL이 보여질 것인지에 대한 컨트롤만 있으면 됩니다.Apache에서 PHP를 돌리도록 설정하는 것이 "간단"함에도 불구하고, 거기에조차 몇 가지의 보이지 않는 함정이 숨어 있습니다. PHP 문서에서 .php
파일을 PHP로 돌리기 위해서 SetHandler
를 권장하는데, AddHandler
는 잘 동작하는 것처럼 보이지만 여기에 문제가 있습니다.
AddHandler를 사용할 때 당신은 Apache에게 "이것을 PHP로 실행하라"는 것이 .php
파일을 핸들링하기 위한 유일한 방법이라고 전달합니다. 하지만! Apache는 파일 확장자에 관해서 지구상의 어떤 인간들과도 다른 아이디어를 가지고 있습니다. Apache는 가령 index.html.en
같은 파일은 HTML과 영어로 인식하도록 설계되었습니다. Apache에게 파일은 여러 개의 확장자를 동시에 가질 수 있는 것입니다.
만약 파일 업로드 폼이 있고 그것을 통해 어떤 public 디렉토리에 파일을 저장할 수 있다고 상상해 봅시다. 사람들이 PHP 파일을 올리지 못하게 하기 위하여 .php
확장자를 검사하는 기능을 넣을 것입니다. 만약 공격자가foo.php.txt
라는 파일을 업로드하면 문제없이 업로드가 될 것입니다. 하지만 Apache는 그것을 PHP로 인식하고 실행하게 될 것입니다.
문제는 "원래 파일 이름을 사용하지 않는다", "꼼꼼히 검사하지 않는다" 같은 것이 아닙니다. 문제는 웹 서버가 어디에나 널려 있는 코드를 실행시킬 수 있다는 점입니다. 이것은 PHP가 "배치하기 쉽다"는 것과 일치하는 속성이기도 합니다. CGI는 +x
가 필요합니다. 하지만 PHP에서는 그런 것을 할 필요가 없습니다. 그리고 이것은 단지 이론적인 문제가 아닙니다. 이런 문제를 겪은 여러 사이트들을 봤습니다.
아래의 기능들 모두 웹 어플리케이션을 개발할 때 여러가지 면에서 중요하다고 생각합니다. PHP가 "웹 프로그래밍 언어"로 팔리고 있기 때문에 이런 기능들의 일부라도 구현되어 있으면 합리적일 것입니다.
htmlspecialchars
쓰는 걸 잊지 마세요" 같은게 아니라 이런 거요.mod_rewrite
(와 일반적으로.htaccess
)를 사용하는 것이 괜찮은 대안이라고 속아 왔습니다.PHP의 보안에 대한 나쁜 평판의 대부분이 임의의 언어로 된 데이터를 받아서 다른 언어로 그대로 내놓는다는 점에서 옵니다. "<script>"
는 SQL에서는 아무런 의미가 없지만 물론 HTML에선 있습니다.
이걸 더욱 악화시키는 것은 "입력을 정화(sanitize)하십시오" 라는 공통적인 외침입니다. 이건 완전히 잘못된 것입니다. 한번 휘두르면 데이터 더미를 본질적으로 "깨끗하게" 만드는 마법의 지팡이 같은 것은 없습니다. 단지 필요한 것은 언어를 쓰는 것입니다. SQL에 플레이스홀더(placeholder)를 단다거나 프로세스를 호출할 때 인자 목록을 쓴다거나 등등....
addslashes
, stripslashes
같은 슬래쉬 관련 함수들은 완전히 말도 안되고 도움도 안되는 빨간 청어(역주: 중요한 것에서 집중을 분산시키는 것이라는 의미)입니다.pcntl_fork
와 pcntl_exec
를 쓰는 방법 외에는 없습니다.escapeshellcmd
와 escapeshellarg
두개가 거의 비슷한 설명과 함께 있습니다. 하지만 escapeshellarg
는 윈도에서 돌지 않고 (Bourne 쉘의 형식을 가정하기 때문에) escapeshellcmd
는 단지 마침표를 공백으로 치환할 뿐입니다. (뭔가를 하려고 하면 그냥 조용하게 죽어버릴 수도 있습니다)지금까지 SQL 인젝션에 대한 PHP 문서에서는 타입 검사, sprintf
나 is_numeric
사용,mysql_real_escape_string
을 어디서든지 직접 사용, (심지어 도움이 될지 안될지 모르는!)addslashes
를 직접 사용하는 등의 제 정신이 아닌 것 같은 대응 방법을 추천하고 있습니다. 유저 코멘트란을 제외하고 PDO나 파라미터화(parameterization)의 언급은 하나도 없습니다. 2년 전에 PHP 개발자들에게 이 점을 매우 구체적으로 이야기했고 개발자들은 들었습니다. 하지만 이 페이지는 아직까지 바뀌지 않았습니다.
register_globals
. 한동안 기본적으로 꺼져 있었고 5.4에서는 없어졌습니다. 뭐 상관 없습니다. 이건 부끄러워 할만한 것입니다.include
또한 HTTP URL을 받아들일 수 있습니다.libxml_disable_entity_loader()
만이 이걸 막을 수 있습니다. 더 문제는 이 내용이 소스코드 내 주석에만 적혀있다는 거죠.PHP 인터프리터 자체에 정말 끝내주는 보안 문제가 몇 차례 있었습니다.
if (size > INT_MAX) return NULL;
로 시작되어서 그대로 막장으로 갔습니다. (C에 익숙하지 않은 분들을 위해서: INT_MAX
는 하나의 변수에 담을 수 있는 가장 높은 숫자입니다. 이제 나머지를 이해하실 수 있을 거라고 믿습니다.)crypt()
함수로 인해 누구나 패스워드 없이 로그인을 할 수 있는 버그가 발견되었습니다.Content-Length
헤더(임의로 설정가능한)를 받아서 그만큼의 메모리를 할당하려고 시도하기 때문입니다. 이건 매우 나쁜 아이디어입니다.찾아보면 더 나오겠지만 X개의 보안 취약점이 있다는 것은 요지가 아닙니다. 소프트웨어는 버그가 있고 뭐든 일어날 수가 있기 때문이죠. 하지만 이것들의 본질이 끔찍하다는 겁니다. 그리고 이것들은 제가 찾은 것이 아닙니다. 최근 몇 달동안 갑자기 저희 집 문 앞에 나타났을 뿐입니다.
몇몇 코멘트에서 저한테 결론이 없다는 것을 잘 지적했습니다. 네, 뭐 저는 결론이 없습니다. 여기까지 읽어보셨다면 시작하기도 전에 저에게 동의했을 거라고 추정했죠.
PHP만 알고 있는 상태고 다른 것을 배울 의향이 있다면 Python 튜토리얼을 읽어보시고 웹 개발에서는 Flask를 써 보세요. (저는 템플릿 언어의 팬은 아닙니다만 그래도 괜찮긴 합니다.) 그것은 여러분의 앱을 나누지만 모두 다 같은 조각들이고 충분히 친숙하게 보일 겁니다. 여기에 대해서는 나중에 여기에 속하지 않는 언어와 웹 스택 전반을 소개하는 블로그 포스팅에서 따로 다루겠습니다.
그리고 이후에 더 큰 프로젝트를 하실 거라면 중간 레벨에 있는 Pyramid가 좋습니다. Django도 있습니다. Django 사이트와 비슷한 것을 만들기에 적합한 엄청나게 거대한 괴물이죠.
만약 개발자가 아닌데 어떤 이유로 이 글을 읽으셨다구요? 지구상의 모든 사람들이 Learn Python The Hard Way를 정독하기 전까지 전 행복해지지 않을 겁니다. 그러니 읽으세요.
써본 적은 없지만 Ruby와 Rails나 경쟁자들이 있고 Perl 역시 Catalyst와 함께 여전히 잘 살아 있습니다. 끊임없이 읽고 배우고 만들고 열중하세요.
이하에 감사드립니다.