Devin.KR

PHP 시작하기 - CLI 와 내장 웹 서버

개발자KR 조회 0

이 장에서 배우는 것

이 장에서는 PHP 를 처음 실행해 본다. 이 책은 HTML 만 아는 사람이 동네 빵집의 온라인 예약 주문 사이트를 장마다 조금씩 키워 가며 PHP 를 익히는 과정을 따라간다. 첫 장의 목표는 단순하다. 내 컴퓨터에서 PHP 가 실제로 돌아가는 것을 확인하고, PHP 가 HTML 을 어떤 식으로 만들어 내는지 눈으로 본다. 문법 규칙은 거의 나오지 않는다. 대신 이후 모든 장에서 반복해서 쓸 실행 방법 세 가지를 손에 익힌다.

  • 터미널에서 php -v 로 설치된 PHP 의 버전을 확인할 수 있다.
  • php 파일이름.php 로 PHP 파일을 실행하고 출력을 읽을 수 있다.
  • php -S 로 내장 웹 서버를 띄우고 브라우저에서 결과를 볼 수 있다.
  • PHP 태그 안팎의 차이를 설명하고, echo 로 HTML 을 출력할 수 있다.

문제 상황

이 책의 예제 도메인은 '온누리 빵집'이다. 사장님은 지금까지 예약을 전화와 메모지로 받았다. 명절 앞뒤로 주문이 몰리면 전화를 놓치고, 메모지가 섞여 같은 케이크를 두 번 만드는 일도 생긴다. 그래서 웹에서 예약을 받는 사이트를 만들기로 했다.

가장 먼저 만들 화면은 '오늘의 빵' 안내 페이지다. HTML 만 알아도 이 화면은 만들 수 있다. 다만 HTML 파일은 적어 둔 내용을 그대로 보여 줄 뿐이다. 내일 소금빵이 다 팔려서 크루아상으로 바뀌면 파일을 열어 손으로 고쳐야 한다. 예약 접수, 재고 표시, 주문 목록처럼 상황에 따라 내용이 달라지는 화면은 프로그램이 HTML 을 만들어 줘야 한다. 이 역할을 맡는 언어가 PHP 다.

처음 PHP 를 접한 사람이 흔히 겪는 일이 하나 있다. 예제 파일을 만들고 브라우저에 끌어다 놓거나 더블클릭해서 열면, 화면에 결과가 아니라 코드가 그대로 나오거나 빈 화면이 나온다. PHP 파일은 HTML 파일과 달리 열기만 해서는 동작하지 않고, 누군가 PHP 를 실행해 줘야 하기 때문이다. 이 장은 그 실행 방법을 익히는 장이다.

이 장에서 만드는 결과물은 작다. 빵집 이름과 오늘의 빵 한 가지, 가격이 담긴 HTML 문서 하나를 PHP 가 출력하게 한다. 내용은 아직 고정이지만, 다음 장부터 여기에 변수와 조건이 붙으면서 살아 있는 페이지가 된다.

명령줄에서 PHP 실행하기

명령줄 인터페이스(CLI, command-line interface)는 글자로 명령을 입력해 프로그램을 실행하는 방식이다. macOS 의 터미널이나 Linux 의 터미널 창이 이에 해당한다. PHP 는 웹 서버 안에서만 도는 도구로 알려져 있지만, 터미널에서 직접 실행할 수도 있다. 이 책의 모든 완성 코드는 터미널에서 php main.php 처럼 실행해 결과를 확인한다.

php -v 로 버전 확인하기

먼저 PHP 가 설치되어 있는지 확인한다. 터미널에서 다음 명령을 입력한다.

php -v

설치되어 있다면 다음과 비슷한 출력이 나온다. 세부 숫자와 빌드 날짜는 환경마다 다르다.

PHP 8.4.x (cli) (built: ...) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.4.x, Copyright (c) Zend Technologies

첫 줄의 8.4 가 버전이고, 괄호 안의 cli 는 명령줄용 실행 파일이라는 뜻이다. 이 책은 8.4 이상을 기준으로 쓴다. command not found 같은 메시지가 나오거나 버전이 8.3 이하라면 설치나 업그레이드가 필요하다. macOS 에서는 패키지 관리자 Homebrew 로, Linux 에서는 배포판의 패키지 관리자로 설치하는 것이 일반적이다. 자세한 절차는 PHP 공식 설치 안내를 참고한다.

PHP 파일을 실행하기

작업할 폴더를 만들고 그 안으로 이동한다. 폴더 이름은 마음대로 정해도 된다.

mkdir bakery
cd bakery

이 폴더에 텍스트 편집기로 main.php 파일을 만든다. 파일은 UTF-8 인코딩으로 저장해야 한다. 한글이 깨지는 문제의 대부분은 다른 인코딩으로 저장해서 생긴다. 파일을 만들었다면 다음처럼 실행한다.

php main.php

이 명령에서 php 는 실행할 프로그램, main.php 는 그 프로그램에 넘기는 값이다. 명령 뒤에 붙이는 값을 인수(argument)라고 부른다. PHP 는 파일을 위에서 아래로 읽으며 실행하고, 파일 끝에 도달하면 종료한다. 종료 후에는 프로그램이 남아 있지 않다. 웹 서버 없이 결과만 터미널에 나온다는 점이 CLI 실행의 특징이다.

내장 웹 서버로 브라우저에서 보기

웹 페이지는 결국 브라우저가 웹 서버에 요청(request)을 보내고, 서버가 응답(response)을 돌려주는 과정이다. 보통은 Apache 나 Nginx 같은 웹 서버 프로그램을 따로 설치하고 설정해야 하지만, PHP 는 학습과 개발용으로 쓸 수 있는 내장 웹 서버(built-in web server)를 기본으로 갖고 있다. 설치할 것도, 설정 파일도 없다.

php -S localhost:8000

옵션 -S 뒤에는 '주소:포트'를 적는다. localhost 는 내 컴퓨터 자신을 뜻하는 주소이고, 포트(port)는 한 컴퓨터 안에서 프로그램을 구분하는 번호다. 8000 은 관례로 자주 쓰는 번호일 뿐이며 다른 프로그램이 쓰고 있지 않은 번호면 무엇이든 된다. 실행하면 다음과 같은 안내가 나오고 터미널이 그 상태로 멈춰 있다.

PHP 8.4.x Development Server (http://localhost:8000) started

멈춘 것이 아니라 요청을 기다리는 중이다. 이 상태에서 브라우저 주소창에 http://localhost:8000/ 을 입력하면 서버가 현재 폴더의 index.php 를 실행해 결과를 응답한다. 서버를 끝낼 때는 터미널에서 Ctrl+C 를 누른다. 서버는 요청이 올 때마다 PHP 파일을 새로 읽어 실행하므로, 파일을 고친 뒤에는 서버를 다시 켤 필요 없이 브라우저를 새로 고침하면 된다.

내장 서버는 내 컴퓨터에서 개발할 때 쓰는 도구다. 성능과 보안 기능이 실제 서비스용 웹 서버에 미치지 못하므로 인터넷에 공개하는 용도로 쓰지 않는다. 옵션의 전체 목록은 내장 웹 서버 문서에 있다. 이 장에서 쓰는 명령을 표로 정리하면 다음과 같다.

이 장에서 쓰는 PHP 명령과 용도
명령하는 일주로 쓰는 때
php -v설치된 버전을 출력하고 끝난다설치 직후, 환경 점검
php main.php파일을 한 번 실행하고 끝난다코드 동작을 터미널에서 확인
php -S localhost:8000현재 폴더를 대상으로 서버를 띄운다브라우저로 페이지 확인
php -S localhost:8000 -t publicpublic 폴더를 문서 루트로 서버를 띄운다공개 파일만 따로 두는 구조

마지막 줄의 -t 옵션은 문서 루트(document root)를 정한다. 문서 루트는 서버가 주소의 / 를 어느 폴더에 대응시킬지 정한 기준 폴더다. 지정하지 않으면 명령을 실행한 현재 폴더가 문서 루트가 된다.

아래 그림은 브라우저에서 주소를 열었을 때 벌어지는 일을 순서대로 보여 준다.

브라우저는 서버가 PHP 를 실행한 뒤 만들어 낸 HTML 만 응답으로 받는다.

그림에서 핵심은 아래쪽 화살표다. 브라우저에 도착하는 것은 PHP 코드가 아니라 실행이 끝난 HTML 이다. 그래서 브라우저에서 '페이지 소스 보기'를 해도 PHP 코드는 보이지 않는다.

PHP 가 HTML 을 만드는 방식

PHP 태그 안과 밖

PHP 파일은 두 종류의 영역이 섞인 텍스트다. 하나는 <?php 로 시작해 ?> 로 끝나는 PHP 코드 영역이고, 다른 하나는 그 바깥의 나머지 영역이다. PHP 는 파일을 읽다가 태그 바깥의 글자는 손대지 않고 그대로 출력하며, 태그 안의 코드는 실행해서 그 결과를 출력한다. 그래서 HTML 문서에 PHP 태그를 군데군데 끼워 넣으면, 태그 바깥은 HTML 그대로 나가고 태그 안은 실행 결과로 바뀐다.

순수하게 PHP 코드만 있는 파일에서는 끝의 ?> 를 생략하는 것이 관례다. 파일 끝에 닫는 태그와 함께 빈 줄이 남으면 그 빈 줄이 출력에 섞이는 사고가 나기 때문이다. 닫는 태그는 HTML 사이에 코드를 끼워 넣을 때만 쓴다.

짧게 값을 출력할 때 쓰는 <?= 값 ?> 형태도 있다. 이는 <?php echo 값; ?> 와 같은 뜻이다. 다음 그림은 HTML 안에 끼워 넣은 PHP 태그 한 줄이 어떻게 바뀌는지 보여 준다.

PHP 태그 안의 코드는 실행 결과로 바뀌고 태그 바깥의 글자는 그대로 출력된다.

한 가지 세부 규칙이 있다. 닫는 태그 ?> 바로 뒤에 오는 줄바꿈 한 개는 출력되지 않고 PHP 가 삼킨다. 그래서 파일 첫 줄에 <?php ... ?> 만 적고 줄을 바꿔도 출력 맨 앞에 빈 줄이 생기지 않는다. 뒤에 공백이나 글자가 더 있으면 그것은 그대로 출력된다.

echo 와 따옴표

echo 는 값을 출력하는 구문이다. 함수처럼 보이지만 언어에 내장된 구문이라서 괄호가 필요 없다. 쉼표로 여러 값을 이어 적으면 왼쪽부터 차례대로 출력한다. 줄 끝에는 반드시 세미콜론(;)을 붙인다. 세미콜론은 문장이 끝났다는 표시이며, 빠뜨리면 실행 전에 구문 오류가 난다.

글자를 따옴표로 감싼 값을 문자열(string)이라고 한다. PHP 에는 큰따옴표와 작은따옴표 두 가지가 있고 동작이 조금 다르다.

큰따옴표 문자열과 작은따옴표 문자열의 차이
표기\n 의 해석따옴표 안에 같은 따옴표어울리는 용도
"..."줄바꿈 문자로 해석한다\" 로 적어야 한다줄바꿈이 필요한 짧은 문자열
'...'백슬래시와 n 두 글자 그대로다\' 로 적어야 한다HTML 처럼 큰따옴표가 많은 문자열

HTML 속성은 lang="ko" 처럼 큰따옴표를 쓰는 일이 많으므로, HTML 조각은 작은따옴표로 감싸고 줄바꿈만 "\n" 으로 따로 출력하면 백슬래시를 늘어놓지 않아도 된다. 이 책도 그렇게 쓴다. 큰따옴표 문자열은 변수의 값을 글자 사이에 끼워 넣는 기능도 갖고 있는데, 변수는 다음 장에서 배우므로 지금은 다루지 않는다.

출력에 줄바꿈이 필요한 이유도 알아 둔다. HTML 은 소스에서 줄을 바꿔도 화면에서는 한 덩어리로 보이므로 브라우저에서는 티가 나지 않는다. 하지만 CLI 에서는 echo 가 줄바꿈을 자동으로 넣어 주지 않아서, 직접 "\n" 을 출력하지 않으면 모든 줄이 한 줄로 이어진다.

마지막으로, 코드 첫머리의 declare(strict_types=1); 는 함수에 값을 넘길 때 타입 검사를 엄격하게 하겠다는 선언이다. 이 장에서는 아직 그 효과가 보이지 않는다. 이 책의 모든 파일이 같은 첫머리를 갖도록 지금부터 습관으로 들인다. 이 선언은 파일의 첫 실행문이어야 한다.

완성 코드

작업 폴더에 파일 두 개를 만든다. main.php 는 터미널에서 실행하는 프로그램이고, index.php 는 브라우저로 볼 페이지다. 두 파일은 완전히 같은 HTML 을 만들어 낸다. 하나는 echo 로 문서 전체를 만들고, 다른 하나는 HTML 문서 안에 PHP 태그를 끼워 넣는 방식이다.

main.php

<?php
declare(strict_types=1);

// 빵집 안내 페이지를 HTML 로 한 줄씩 만들어 출력한다.
echo '<!DOCTYPE html>', "\n";
echo '<html lang="ko">', "\n";
echo '<head>', "\n";
echo '  <meta charset="utf-8">', "\n";
echo '  <title>온누리 빵집</title>', "\n";
echo '</head>', "\n";
echo '<body>', "\n";
echo '  <h1>온누리 빵집</h1>', "\n";
echo '  <p>오늘의 빵: 소금빵</p>', "\n";
echo '  <p>가격: ', 3600, '원</p>', "\n";
echo '</body>', "\n";
echo '</html>', "\n";

index.php

<?php declare(strict_types=1); ?>
<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="utf-8">
  <title>온누리 빵집</title>
</head>
<body>
  <h1>온누리 빵집</h1>
  <p>오늘의 빵: <?php echo '소금빵'; ?></p>
  <p>가격: <?= 3600 ?>원</p>
</body>
</html>

줄별 해설

main.php

1행의 <?php 는 여기서부터 PHP 코드라는 표시다. 파일 전체가 코드이므로 닫는 태그는 없다. 2행은 앞에서 설명한 declare(strict_types=1); 이며 파일의 첫 실행문 자리에 있다. 3행은 비어 있고, PHP 는 빈 줄을 무시한다. 코드를 의미 단위로 나눠 읽기 좋게 하려는 것이다.

4행의 // 로 시작하는 줄은 주석(comment)이다. 주석은 사람이 읽으라고 남기는 설명이라서 PHP 가 실행하지 않는다. 주석에는 무엇을 하는지보다 왜 하는지를 적는 편이 나중에 도움이 된다.

5행부터 16행까지는 HTML 문서를 한 줄씩 출력하는 echo 문이다. 각 문장은 작은따옴표로 감싼 HTML 조각과, 쉼표 뒤에 오는 "\n" 로 이루어진다. 뒤의 것은 큰따옴표라서 줄바꿈 문자로 해석되어, HTML 한 줄이 출력될 때마다 줄이 바뀐다. 8행과 9행, 12행, 13행의 앞쪽 공백 두 칸은 들여쓰기이며, 작은따옴표 안에 있으므로 출력에 그대로 포함된다.

14행이 이 장에서 가장 볼 만한 줄이다. echo 에 값을 세 개 넘겼다. 작은따옴표 문자열 ' <p>가격: ', 따옴표 없는 숫자 3600, 그리고 문자열 '원</p>' 이다. 숫자는 문자열이 아니어도 echo 가 글자로 바꿔 출력한다. 세 값이 왼쪽부터 이어 붙어 가격: 3600원 이 된다. 마지막의 "\n" 까지 합하면 값이 네 개다.

index.php

1행은 PHP 태그 안에서 declare 만 하고 곧바로 닫는다. 닫는 태그 뒤의 줄바꿈 한 개는 삼켜지므로 출력은 2행의 문서 유형 선언부터 시작한다. 2행부터 9행까지와 12, 13행은 태그 바깥이라서 적힌 그대로 출력된다.

10행에는 <?php echo '소금빵'; ?> 가 있다. 이 부분만 실행되어 소금빵 으로 바뀌고, 앞뒤의 <p>오늘의 빵: 과 </p> 는 그대로 남는다. 11행의 <?= 3600 ?> 도 같은 방식으로 3600 이 된다. 뒤에 붙은 원 은 태그 바깥이므로 그대로 출력된다. 태그 사이의 공백 한 칸은 삼켜지는 줄바꿈이 아니라서 출력에 남는다.

이 파일에는 닫는 태그 뒤에 </html> 로 끝나는 HTML 이 이어질 뿐 마지막에 ?> 가 없다. 그래서 파일 마지막 줄의 줄바꿈이 그대로 출력되어, main.php 의 결과와 글자 하나까지 같다.

실행 결과

작업 폴더에서 main.php 를 실행한다.

$ php main.php
<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="utf-8">
  <title>온누리 빵집</title>
</head>
<body>
  <h1>온누리 빵집</h1>
  <p>오늘의 빵: 소금빵</p>
  <p>가격: 3600원</p>
</body>
</html>

브라우저에서 보려면 터미널 하나에서 서버를 켜 둔다.

$ php -S localhost:8000

주소창에 http://localhost:8000/ 을 입력하면 큰 제목 '온누리 빵집' 아래에 오늘의 빵과 가격이 표시된다. 화면 대신 글자로 확인하고 싶다면 다른 터미널 창에서 curl 로 같은 주소를 요청해 본다. -s 는 진행 상황 표시를 끄는 옵션이다.

$ curl -s http://localhost:8000/
<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="utf-8">
  <title>온누리 빵집</title>
</head>
<body>
  <h1>온누리 빵집</h1>
  <p>오늘의 빵: 소금빵</p>
  <p>가격: 3600원</p>
</body>
</html>

두 출력이 같다는 점을 확인한다. 브라우저가 받는 것은 php main.php 가 터미널에 찍은 것과 똑같은 HTML 이다. 서버를 끄려면 서버를 켜 둔 터미널에서 Ctrl+C 를 누른다.

실무에서 자주 틀리는 것

세미콜론 빠뜨리기

문장 끝의 세미콜론을 잊으면 실행되기도 전에 구문 오류(Parse error)가 난다. 오류 메시지가 가리키는 줄은 빠뜨린 줄이 아니라 그 다음 줄인 경우가 많다. PHP 가 다음 줄을 읽고 나서야 앞 문장이 끝나지 않았음을 알기 때문이다.

<?php
declare(strict_types=1);

echo '소금빵', "\n"
echo '크루아상', "\n";

오류가 보고된 줄에서 한 줄 위를 살핀다. 고친 코드는 다음과 같다.

<?php
declare(strict_types=1);

echo '소금빵', "\n";
echo '크루아상', "\n";

작은따옴표 안에서 \n 쓰기

작은따옴표 문자열에서는 \n 이 줄바꿈이 되지 않고 글자 그대로 출력된다. 이 실수는 오류가 나지 않아서 알아채기 어렵다.

<?php
declare(strict_types=1);

echo '소금빵\n';
echo '크루아상\n';

위 코드의 출력은 소금빵\n크루아상\n 이라는 한 줄이다. 줄바꿈은 큰따옴표 문자열로 따로 출력하도록 고친다.

<?php
declare(strict_types=1);

echo '소금빵', "\n";
echo '크루아상', "\n";

파일을 브라우저에 직접 열기

PHP 파일을 더블클릭하거나 주소창에 file:///.../index.php 처럼 파일 경로를 넣어 열면, 브라우저가 PHP 를 실행하지 않고 파일 내용을 그대로 보여 주거나 내려받으려 한다. 브라우저에는 PHP 를 실행하는 기능이 없기 때문이다.

file:///Users/me/bakery/index.php

파일이 있는 폴더에서 서버를 켜고 http 주소로 접속해야 한다.

php -S localhost:8000
# 브라우저 주소: http://localhost:8000/index.php

declare 를 첫 실행문 자리에 두지 않기

declare(strict_types=1); 는 파일의 첫 실행문이어야 한다. 그 앞에 echo 같은 다른 문장이 있으면 실행 전에 치명적이지 않은 수준이 아니라 실행 자체가 중단되는 오류가 난다.

<?php
echo '소금빵', "\n";
declare(strict_types=1);

선언을 맨 위로 옮긴다. 주석과 빈 줄은 앞에 있어도 된다.

<?php
declare(strict_types=1);

echo '소금빵', "\n";

한눈에 보기

이 장에서 배운 것 정리
주제기억할 것예
버전 확인8.4 이상인지 첫 줄을 본다php -v
파일 실행위에서 아래로 실행하고 끝나면 종료한다php main.php
내장 서버개발용이며 Ctrl+C 로 끈다php -S localhost:8000
PHP 태그태그 안은 실행하고 바깥은 그대로 출력한다<?php ... ?>, <?= ... ?>
echo쉼표로 여러 값을 출력하고 문장은 세미콜론으로 끝낸다echo '가격: ', 3600;
따옴표줄바꿈은 큰따옴표, HTML 은 작은따옴표가 편하다"\n", '<p>'
strict_types파일의 첫 실행문으로 둔다declare(strict_types=1);

연습 문제

  1. 다음 코드를 php test.php 로 실행했을 때 터미널에 나오는 출력을 정확히 쓰시오.
    <?php
    declare(strict_types=1);
    
    echo "빵", "집\n";
    echo '문\n';
  2. 다음 코드에는 오류가 두 군데 있다. 찾아서 고치시오.
    <?php
    echo '<h1>오늘의 메뉴</h1>', "\n"
    declare(strict_types=1);
    echo '<p>크루아상 2800원</p>', "\n";
  3. menu.php 파일을 만들어 echo 로 <h1>오늘의 메뉴</h1> 줄과 <p>크루아상 2800원</p> 줄을 출력하시오. 숫자 2800 은 문자열이 아니라 숫자로 echo 에 넘기고, 각 줄 끝에서 줄을 바꾼다.
  4. 현재 폴더 아래의 public 폴더를 문서 루트로 삼아 내장 서버를 8080 포트로 띄우는 명령을 쓰시오. 그리고 서버를 끄는 방법도 쓰시오.

정답과 해설

1번. 출력은 다음과 같다. 마지막에 줄바꿈이 없어서 다음 프롬프트가 같은 줄에 이어질 수 있다.

빵집
문\n

첫 echo 는 값 두 개를 이어 빵집 을 출력하고 큰따옴표의 \n 이 줄을 바꾼다. 둘째 echo 는 작은따옴표라서 \n 이 백슬래시와 n 두 글자 그대로 출력되고, 그 뒤에 줄바꿈은 없다.

2번. 오류는 두 군데다. 첫째, 2행 끝에 세미콜론이 없다. 둘째, declare(strict_types=1); 가 첫 실행문 자리에 있지 않다. 고친 코드는 다음과 같다.

<?php
declare(strict_types=1);

echo '<h1>오늘의 메뉴</h1>', "\n";
echo '<p>크루아상 2800원</p>', "\n";

세미콜론이 빠진 줄은 그 자체로도 구문 오류이고, declare 위치 오류는 그다음에 걸린다. 한 번 고치고 다시 실행해 보면 남은 오류를 확인할 수 있다.

3번. 정답 예시는 다음과 같다. 숫자를 따로 넘기려면 문자열을 나누어 쉼표로 이어 준다.

<?php
declare(strict_types=1);

echo '<h1>오늘의 메뉴</h1>', "\n";
echo '<p>크루아상 ', 2800, '원</p>', "\n";

php menu.php 로 실행하면 두 줄이 출력된다. 2800 앞뒤에서 문자열을 끊는 위치, 그리고 문장 끝의 세미콜론을 확인한다.

<h1>오늘의 메뉴</h1>
<p>크루아상 2800원</p>

4번. 명령은 php -S localhost:8080 -t public 이다. -S 는 서버 주소와 포트를, -t 는 문서 루트 폴더를 정한다. 서버는 서버를 켜 둔 터미널에서 Ctrl+C 를 눌러 끈다. 이때 public 폴더 안의 index.php 가 주소 / 에 대응한다.

댓글 0

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

댓글을 남기려면 로그인이 필요합니다.