PHpullh
심층 가이드/PHP/Hello World

PHP · 심층 가이드

PHP Hello World 완전 정리

PHP 파일 한 개를 CLI와 웹 서버 양쪽에서 돌려 보며 echo와 print의 차이, Heredoc, JSON 응답, 내장 서버 라우터까지 첫 출력이 지나가는 경로를 전부 확인합니다.

주제 10개 · 예제 코드 포함 · 최종 수정 2026-09-03 · 작성 pullh 편집팀

PHP는 처음부터 HTML 문서 안에 끼워 넣는 템플릿 도구로 출발했습니다. 그래서 main() 같은 진입 함수가 없고, 파일은 기본적으로 출력 모드로 시작해 <?php 를 만나는 순간부터만 코드로 해석됩니다. 다른 언어를 먼저 배운 사람이 가장 먼저 어색해하는 지점이 여기입니다. 태그 바깥의 공백 한 칸도 그대로 응답 본문이 되고, 같은 파일이 CLI에서는 표준 출력으로, php-fpm 뒤에서는 HTTP 본문으로 흘러갑니다.

순서는 출력 자체에서 시작해 실행 환경으로 넓혀 가면 편합니다. Hello World 출력과 print 와 echo 비교로 표현식과 구문의 차이를 잡은 뒤, HTML 안에 PHP 끼워 넣기에서 태그 경계를 몸으로 익히고, 여러 줄 출력이 지저분해지는 시점에 Heredoc 문자열과 Nowdoc 문자열로 넘어가면 변수 보간을 켜고 끄는 감각이 생깁니다. 뒤쪽의 CLI 인자 읽기와 내장 서버 라우터 스크립트는 같은 코드가 SAPI에 따라 어떻게 달리 동작하는지 보여 주는 짝입니다.

가장 흔하게 발목을 잡는 것은 닫는 태그입니다. 파일 끝의 ?> 뒤에 개행이나 공백이 남아 있으면 그 바이트가 먼저 전송되고, 이후 header()나 세션 시작은 headers already sent 오류로 실패합니다. 편집기가 붙인 UTF-8 BOM도 같은 결과를 냅니다. 그래서 PHP 코드만 들어 있는 파일은 닫는 태그를 아예 쓰지 않는 것이 표준 관행입니다. JSON 응답 보내기를 실습할 때 이 규칙을 어기면 응답 앞에 빈 줄이 붙어 파서가 깨지므로 바로 체감할 수 있습니다.

01Hello World 출력

echo가 문자열 하나를 표준 출력으로 내보내고, 끝의 이스케이프가 줄바꿈으로 해석됩니다. 실행 환경과 파일이 제대로 연결됐는지 확인하는 첫 단계로 쓰입니다. 큰따옴표 안에서만 \n이 개행이 되고 작은따옴표로 감싸면 역슬래시와 n 두 글자가 그대로 나가며, 웹 응답에서는 개행이 HTML 화면에 보이지 않는다는 차이도 있습니다.

PHP code

<?php
echo "Hello, World!
";
알아두면 좋은 점

PHP_EOL을 쓰면 실행 환경의 줄바꿈 문자를 따라가고, 파일 끝의 닫는 태그를 생략하면 뒤의 공백이 응답에 섞이는 사고를 막을 수 있습니다.

자주 하는 실수

echo 뒤 세미콜론을 빼먹으면 곧바로 parse error가 납니다.

02print 와 echo 비교

같은 문자열을 echo와 print로 각각 내보내고, printf()로는 서식 안에 상수 값을 끼워 넣습니다. 출력 방식이 셋 다 다른 자리에 쓰인다는 걸 한눈에 보여 줍니다. print는 항상 1을 반환하는 표현식이라 조건식 안에 넣을 수 있지만 echo는 문이라 불가능하고, 대신 echo만 쉼표로 여러 값을 한 번에 받습니다.

PHP code

<?php
echo "echo
";
print "print
";
printf("php %s
", PHP_VERSION);
알아두면 좋은 점

printf는 출력한 바이트 수를 돌려주고 sprintf는 같은 서식으로 문자열만 만들어 줍니다. 자리표시자 개수와 인자 개수가 맞지 않으면 ArgumentCountError가 납니다.

자주 하는 실수

서식 문자열에 사용자 입력을 그대로 넣으면 값에 든 %s가 서식으로 해석되어 출력이 어긋납니다. 값은 반드시 인자 자리로 넘겨야 합니다.

03CLI 인자 읽기

명령줄에서 넘어온 첫 인자를 읽되, 없으면 널 병합 연산자가 기본값을 채워 넣습니다. 배치 스크립트나 크론 작업에 환경마다 다른 값을 주입할 때의 최소 형태입니다. $argv는 CLI SAPI에서만 채워져 웹 요청으로 같은 파일을 열면 존재하지 않고, $argv[0]은 스크립트 경로라서 실제 첫 인자는 인덱스 1부터입니다.

PHP code

<?php
$name = $argv[1] ?? 'PHP';
echo "Hello, {$name}
";
알아두면 좋은 점

$argc에 인자 개수가 들어 있고, 옵션 형태의 인자는 getopt()가 파싱을 대신해 줍니다. 지금 어떤 환경에서 도는지는 php_sapi_name()으로 확인할 수 있습니다.

자주 하는 실수

$argv는 CLI 실행 환경에서만 채워지므로 웹 요청에서는 같은 방식으로 동작하지 않습니다.

04HTML 안에 PHP 끼워 넣기

PHP 블록에서 값을 준비한 뒤 ?>로 빠져나와 HTML을 쓰고, <?= 축약 태그로 이스케이프한 값을 끼워 넣습니다. 템플릿 엔진 없이 뷰 파일을 쓸 때 그대로 통하는 구조입니다. <?=는 short_open_tag 설정과 무관하게 항상 쓸 수 있고, ENT_QUOTES를 줘야 작은따옴표까지 변환돼 속성값 안에서도 안전합니다.

PHP code

<?php
$title = 'PHP Starter';
?>
<h1><?= htmlspecialchars($title, ENT_QUOTES, 'UTF-8') ?></h1>
알아두면 좋은 점

이스케이프 규칙은 넣는 위치마다 달라 HTML 본문과 속성, 자바스크립트, URL에 각각 다른 처리가 필요합니다. htmlspecialchars의 인코딩 인자는 PHP 8.1부터 기본이 UTF-8입니다.

자주 하는 실수

출력 전에 escape 하지 않으면 XSS 취약점이 생길 수 있습니다.

05Heredoc 문자열

<<<TEXT로 시작하는 heredoc이 여러 줄 문자열을 따옴표 이스케이프 없이 담아냅니다. 메일 본문이나 안내 문구처럼 줄바꿈이 살아 있어야 하는 텍스트에 어울립니다. heredoc 안에서는 큰따옴표처럼 변수와 {$expr}가 그대로 치환되고, PHP 7.3부터는 종료 식별자를 들여쓸 수 있으며 그 들여쓰기만큼 각 줄에서 공백이 제거됩니다.

PHP code

<?php
$message = <<<TEXT
PHP can render
multiple lines easily.
TEXT;

echo $message;
알아두면 좋은 점

heredoc 안에서는 메서드 호출 결과도 중괄호로 감싸 넣을 수 있습니다. 종료 식별자가 있는 줄에는 세미콜론 외에 다른 문자가 오면 안 됩니다.

자주 하는 실수

종료 식별자와 같은 문자열이 본문 줄 맨 앞에 나오면 문자열이 거기서 끊깁니다. 들여쓴 종료 식별자보다 본문 들여쓰기가 얕아도 파스 오류가 납니다.

06Nowdoc 문자열

종료 식별자를 작은따옴표로 감싼 nowdoc이라, 안쪽 내용은 변수 치환 없이 적힌 그대로 문자열이 됩니다. SQL이나 정규식처럼 $와 역슬래시가 섞인 원문을 옮길 때 안전합니다. heredoc과 생김새가 거의 같아 따옴표 유무를 놓치기 쉬운데, 그 한 글자가 보간 여부를 가릅니다. 값 삽입은 여전히 바인딩으로 따로 처리해야 합니다.

PHP code

<?php
$query = <<<'SQL'
SELECT *
FROM users
WHERE active = 1
SQL;

echo $query;
알아두면 좋은 점

nowdoc은 컴파일 시점에 확정되는 리터럴이라 정규식 패턴이나 JSON 예시를 그대로 옮겨 붙이기에 좋습니다. 값이 필요하면 sprintf로 자리표시자를 따로 채웁니다.

자주 하는 실수

여기에 SQL을 적어 두고 값만 문자열 연결로 붙이면 결국 SQL 인젝션이 됩니다. 리터럴이 안전한 것과 쿼리 조립이 안전한 것은 별개입니다.

07환경 변수 읽기

getenv()로 환경 변수를 읽고, 값이 없을 때는 ?:가 기본 문자열로 대신합니다. 배포 단계마다 달라지는 설정을 코드에 박지 않고 주입받는 기본 패턴입니다. 이 함수는 미설정 시 false를 돌려주는데 ?:는 빈 문자열이나 "0"도 falsy로 보고 덮어쓰므로, 빈 값을 의미 있게 다루려면 ??와 $_ENV 쪽을 봐야 합니다.

PHP code

<?php
$appEnv = getenv('APP_ENV') ?: 'local';
echo "env: {$appEnv}
";
알아두면 좋은 점

인자 없이 부르면 모든 환경 변수를 배열로 돌려주며 putenv()로 설정한 값도 함께 보입니다. 웹 환경에서는 $_SERVER에만 실려 오는 값도 있습니다.

자주 하는 실수

환경 변수 값은 언제나 문자열이라 'false'나 '0'도 내용이 있는 문자열입니다. 그대로 조건에 쓰면 꺼 두려던 기능이 켜진 채 배포됩니다.

08JSON 응답 보내기

응답 헤더로 JSON 타입을 선언한 뒤 배열을 json_encode()로 직렬화해 그대로 출력합니다. 프레임워크 없이 API 엔드포인트 하나를 만들 때 필요한 최소 구성입니다. JSON_UNESCAPED_UNICODE를 빼면 한글이 \uD55C 형태로 이스케이프되고, header()는 본문이 한 바이트라도 나가기 전에 호출해야 효력이 있습니다.

PHP code

<?php
header('Content-Type: application/json; charset=utf-8');
echo json_encode([
    'language' => 'php',
    'version' => PHP_VERSION,
], JSON_UNESCAPED_UNICODE);
알아두면 좋은 점

값에 UTF-8이 아닌 바이트가 섞이면 인코딩이 실패해 false가 돌아오므로 반환값 확인이 필요합니다. 배열 키가 0부터 이어지지 않으면 JSON 배열이 아니라 객체로 나갑니다.

자주 하는 실수

header 호출 전에 출력이 이미 발생하면 헤더를 보낼 수 없습니다.

09Composer 자동 로딩 시작점

Composer가 만든 vendor/autoload.php를 불러오는 것만으로 설치된 패키지의 클래스들을 곧바로 쓸 수 있게 됩니다. 진입점 파일 맨 위에 오는 관례적인 한 줄입니다. require는 파일이 없으면 치명적 오류로 즉시 중단시켜 include의 경고와 다르고, 오토로더는 클래스가 처음 참조되는 순간에야 해당 파일을 읽으므로 미사용 패키지는 로드되지 않습니다.

PHP code

<?php
require __DIR__ . '/vendor/autoload.php';

echo "autoload ready
";
알아두면 좋은 점

오토로더는 클래스가 처음 필요할 때 파일을 찾으므로, 배포할 때 최적화된 클래스 맵을 만들어 두면 파일 탐색 비용이 줄어듭니다.

자주 하는 실수

vendor/autoload.php 경로를 잘못 잡으면 첫 줄에서 바로 fatal error가 발생합니다.

10내장 서버 라우터 스크립트

요청 URI가 특정 경로면 짧은 응답을 내고 return으로 끝내며, 아니면 기본 응답으로 흘러갑니다. php -S 내장 서버에 라우터 파일로 넘겨 로컬 확인용 엔드포인트를 만들 때 쓰입니다. REQUEST_URI에는 쿼리스트링이 붙어 있어 /health?x=1은 이 비교를 통과하지 못하므로 parse_url()로 경로만 떼는 편이 낫고, return false를 돌려주면 서버가 정적 파일을 대신 처리합니다.

PHP code

<?php
if ($_SERVER['REQUEST_URI'] === '/health') {
    echo 'ok';
    return;
}

echo 'home';
알아두면 좋은 점

내장 서버는 기본적으로 요청을 하나씩 처리해서 스스로에게 HTTP 요청을 보내면 응답을 기다리다 멈춥니다. 개발 확인용으로만 쓰고 운영에는 쓰지 않습니다.

자주 하는 실수

REQUEST_URI에는 쿼리스트링이 붙어 있어 /health?debug=1이 이 비교를 통과하지 못하고 홈 응답으로 떨어집니다. 경로만 떼어 비교해야 합니다.

정리하며

  • 코드만 있는 파일은 닫는 ?> 를 생략해 의도치 않은 출력과 헤더 오류를 막습니다.
  • echo는 여러 인자를 받는 구문, print는 항상 1을 돌려주는 표현식입니다.
  • 변수 보간이 필요하면 Heredoc, 원문 그대로 두려면 Nowdoc을 씁니다.
  • 같은 스크립트라도 CLI는 $argv, 웹은 슈퍼글로벌로 입력을 받으니 진입점을 나눠 둡니다.

더 깊이 들어가고 싶다면 PHP 학습 라이브러리에서 다른 주제 가이드를 이어서 보거나, 언어 비교에서 같은 개념이 다른 언어에서 어떻게 표현되는지 확인해 보세요.