본 수업은 웹 애플리케이션 만들기 수업으로 대체 되었고, 2015년 6월 이후에 폐지됩니다. 개편된 수업에서 뵙겠습니다.http://opentutorials.org/course/1688
PHP란?
PHP는 서버 쪽에서 실행되는 언어다. 사용자가 URL을 브라우저에 입력해서 서버에 접근하면 서버는 그에 해당하는 PHP 파일을 읽어서 이를 PHP의 문법에 맞게 해석한 후에 그 결과를 사용자에게 알려준다.
PHP의 기본
PHP를 사용하기 위해서는 파일의 확장자를 PHP로 지정해야 한다. 사용자가 요청한 URL의 확장자가 PHP라면 시스템은 이 파일을 PHP의 문법에 맞게 해석하기 때문이다. 예를 들어, 사용자가 아래와 같은 URL로 접근했다면
서버는 test.php 파일을 PHP의 문법에 따라서 해석해서 그 결과를 사용자에게 보여준다.
PHP는 HTML과 혼용해서 사용할 수 있는데 아래의 예제를 보자.
<html> <body> Current time is <?php echo date('G:i:s'); ?> (by php) <br /> <script type="text/javascript"> var d = new Date(); var curr_hour = d.getHours(); var curr_min = d.getMinutes(); var curr_sec = d.getSeconds(); document.write('Current time is '+curr_hour+":"+curr_min+":"+curr_sec+" (by javascript)"); </script> </body> </html>
그 결과는 아래와 같다. (브라우저를 리로드하면 계속 시간이 갱신된다.)
예제의 목적은 두 가지다. 1.PHP가 어떻게 구동하는지를 보여주는 것과 2. 클라이언트 사이드 언어인 자바스크립트와 서버 사이드 언어인 PHP의 차이점을 보여주는 것이다.
PHP는 HTML 코드와 섞여서 사용되기 때문에 서버가 PHP를 해석하기 위해서는 PHP 구간을 명시해야 한다. 아래의 예를 보자.
<?php echo date('G:i:s'); ?>
PHP 엔진은 이 문서 중에서 '<?php' 와 '?>' 사이의 구간만을 PHP 문법에 따라서 해석하고 그에 따라 동작한다. echo는 문자를 출력하라는 의미이고 date('G:i:s'); 는 현재 시각을 의미한다.
위의 예제에서 자바스크립트 구간을 발취한 아래 코드를 보자.
<script type="text/javascript"> var d = new Date(); var curr_hour = d.getHours(); var curr_min = d.getMinutes(); var curr_sec = d.getSeconds(); document.write('Current time is '+curr_hour+":"+curr_min+":"+curr_sec+" (by javascript)"); </script>
위의 자바스크립트 코드는 이전의 PHP 코드와 완전히 동일한 역할을 수행한다. 차이가 있다면 전자는 서버 측에 위치한 PHP 엔진이 해석하고, 후자는 클라이언트에 설치된 브라우저가 해석한다는 점이다. PHP 엔진은 서버 컴퓨터에서 동작하고, 브라우저는 사용자의 컴퓨터에서 동작한다는 의미다. 만약 서버 측 컴퓨터의 시간과 사용자 컴퓨터의 시간이 다르다면 시간은 다르게 표시될 것이다. 위의 예제를 실행한 결과를 코드로 열어보면 그 차이를 좀 더 분명하게 이해할 수 있다. 아래 코드를 보자.
<html> <body> Current time is 14:57:25 (by php) <br /> <script type="text/javascript"> var d = new Date(); var curr_hour = d.getHours(); var curr_min = d.getMinutes(); var curr_sec = d.getSeconds(); document.write('Current time is '+curr_hour+":"+curr_min+":"+curr_sec+" (by javascript)"); </script> <body> </html>
PHP 코드가 사라졌다. 왜 그럴까? PHP 엔진이 echo data('G:i:h');의 결과 값인 14:57;25를 HTML 코드 상에 포함시킨 후에 그 결과를 사용자의 브라우저로 전송했기 때문이다. 브라우저는 PHP의 문법을 전혀 모른다. 단지 HTML을 해석할 뿐이다. 반대로 자바스크립트는 브라우저에 의해서 해석된다. 이 관계를 곰곰이 따져보자.
PHP와 데이터베이스
PHP 파일 내에 데이터베이스를 제어하는 구문이 있을 경우 PHP는 아래와 같이 동작한다.
- 데이터베이스 서버에 접속한다.
- 데이터베이스를 선택한다.
- SQL을 데이터베이스로 전송한다.
- 데이터베이스는 PHP가 전송한 SQL을 실행하고 그 결과를 PHP로 다시 전송한다.
- PHP는 데이터베이스가 전송한 결과를 이용해 후속 작업을 하거나 HTML 코드 안에 포함시킨다.
아래의 예제를 보자.
<html> <body> <?php $link = mysql_connect('localhost', 'mysql_user', 'mysql_password'); mysql_select_db('db_name'); $result = mysql_query('SELECT name FROM employee'); echo mysql_result($result, 2); ?> </body> </html>
- mysql_connect는 데이터베이스에 접속하는 명령이다. 데이터베이스 시간에 MySQL Monitor를 언급했었다. 이 명령은 MySQL Monitor를 실행할 때 인증하는 'mysql -uroot -p' 명령과 같은 역할을 한다. 그렇게 보면 PHP도 MySQL 서버의 입장에서는 클라이언트인 것이다.
참고로 실제 서비스에서는 코드 자체에 비밀번호를 입력하지 않는다. 코드가 누출되면 시스템도 뚫리기 때문이다. 비밀번호는 별도의 파일에 변수로 할당하고, 그 파일을 읽어서(include를 참고한다) 변수를 호출한다. 그리고 비밀번호가 기록된 별도의 파일은 버전관리에서 제외한다.
- mysql_select_db는 데이터베이스를 선택하는 명령이다. MySQL Monitor로치면 use db_name에 해당한다.
- mysql_query의 인자로 SELECT name FROM employee를 전달하면 PHP 엔진은 데이터베이스 서버에게 이 SQL문을 질의한다.
- 데이터베이스 서버는 SELECT name FROM employyee를 실행하고 그 결과를 PHP 엔진에게 알려준다.
- PHP엔진은 그 결과를 가지고 있다가 mysql_result 명령을 만나면 결과를 반환한다.
- echo는 뒤에 따라오는 문자열을 출력하는 명령이기 때문에 mysql_result가 반환한 결과 값을 출력한다.
opentutorials PHP 실습
지금까지 우리는 두 가지 중요한 일을 했다.
- UI를 만들었다.
- 데이터베이스를 구축했다.
이제 해야 할 일은 이 두 가지 이질적인 작업을 하나로 묶는 것이다. PHP가 나설 때다.
지금까지 index.html 파일을 이용해서 작업했다. 이제부터는 PHP 에플리케이션을 만들 것이기 때문에 index.php 파일을 생성하고 아래의 코드를 복사&붙여넣기한다.
<?php // 1. 데이터베이스 서버에 접속 $link=mysql_connect('localhost','root','111111'); if(!$link) { die('Could not connect: '.mysql_error()); } // 2. 데이터베이스 선택 mysql_select_db('opentutorials'); mysql_query("set session character_set_connection=utf8;"); mysql_query("set session character_set_results=utf8;"); mysql_query("set session character_set_client=utf8;"); if(!empty($_GET['id'])) { $sql="SELECT * FROM topic WHERE id = ".$_GET['id']; $result = mysql_query($sql); $topic = mysql_fetch_assoc($result); } ?> <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <style type="text/css"> body { font-size: 0.8em; font-family: dotum; line-height: 1.6em; } body.black { background-color: black; color: white; } body.black a { color: white; } body.black a:hover { color: #f60; } body.black header { border-bottom: 1px solid #333; } body.black nav { border-right: 1px solid #333; } header { border-bottom: 1px solid #ccc; padding: 20px 0; } #toolbar { padding: 10px; float: right; } nav { float: left; margin-right: 20px; min-height: 500px; border-right: 1px solid #ccc; } nav ul { list-style: none; padding-left: 0; padding-right: 20px; } article { float: left; } footer { clear: both; } a { text-decoration: none; } a:link, a:visited { color: #333; } a:hover { color: #f60; } h1 { font-size: 1.4em; } .description{ width:500px; } </style> </head> <body id="body"> <div> <header> <h1>JavaScript</h1> </header> <div id="toolbar"> <input type="button" value="black" onclick="document.getElementById('body').className='black'" /> <input type="button" value="white" onclick="document.getElementById('body').className='white'" /> </div> <nav> <ul> <?php $sql="select id,title from topic"; $result=mysql_query($sql); while($row=mysql_fetch_assoc($result)) { echo " <li> <a href=\"?id={$row['id']}\">{$row['title']}</a></li>"; } ?> </ul> </nav> <article> <?php if(!empty($topic)){ ?> <h2><?=$topic['title']?></h2> <div class="description"> <?=$topic['description']?> </div> <?php } ?> </article> </div> </body> </html>
웹브라우저로 http://localhost/opentutorials/index.php 에 접속해서 index.php를 실행한다. 그리고 사이드바의 토픽을 클릭해보자. 아래와 같은 화면이 보인다면 정말 어려운 길을 잘 따라온 것이다. 이게 어떻게 동작하는지 가벼운 마음으로 들어보시라!
이전 수업에서 작업한 index.html 대비 달라진 부분을 중심으로 따져보자. 주석으로 설명을 첨부했다.
참고 주석이란 해석되지 않는, 코드로 인정되지 않는 코드를 의미한다. 자주 사용되는 주석은 아래와 같다.
1. // : '//' 를 선두로 따라오는 코드는 무시된다.
2. /* */ : '/*'와 '*/' 사이의 코드는 무시된다.
<?php // 데이터베이스 서버에 접속 $link=mysql_connect('localhost','root','111111'); if(!$link) { die('Could not connect: '.mysql_error()); } // opentutorials 데이터베이스 선택 mysql_select_db('opentutorials'); // 인코딩과 관련된 부분. 지금은 이해할 필요가 없다. mysql_query("set session character_set_connection=utf8;"); mysql_query("set session character_set_results=utf8;"); mysql_query("set session character_set_client=utf8;"); // 아래에서 별도로 설명하겠다. if(!empty($_GET['id'])) { // $sql 이라는 변수에 SQL 문을 저장한다. $sql="SELECT * FROM topic WHERE id = ".$_GET['id']; $result = mysql_query($sql); $topic = mysql_fetch_assoc($result); } ?>
얼마 안 되는 코드지만 상당히 많은 개념이 응축되어 있다. 힘껏 설명을 해보겠지만, 이것을 이해하지 못해도 괜찮다. 이번 수업의 목표는 PHP를 마스터하는 것이 아니라, PHP가 웹개발에서 어떤 역할을 하는가를 이해하는 것이고, 이것을 이해하는 것은 어렵지 않은 일이기 때문이다. 자 그럼 조금 복잡한 이야기를 해보자.
파라미터
우리가 만들려고 하는 웹서비스는 토픽의 목록을 클릭하면 그 토픽에 해당하는 제목과 본문을 오른쪽 컨텐츠 영역에 출력하는 기능성을 골자로 하고 있다. 사이드바의 토픽 목록의 URL을 열거해보면 아래와 같다. (id 뒤에 따라오는 숫자는 각자 화면과 다를 수 있다)
규칙성을 발견할 수 있다. URL 뒤에 물음표(?)가 따라오고 그 뒤에 'id='이 따라오고 숫자가 붙는다. 숫자는 토픽마다 달라진다. PHP는 이 숫자를 기준으로 사용자가 어떤 데이터를 요청하는지를 판단한다. URL에서 index.php 뒤에 따라오는 '?' 뒤의 문자를 파라미터(parameter)라고 한다. 즉 '?'는 URL과 파라미터를 구분해주는 역할을 한다. 'id=숫자'라고 적혀있는 부분은 파라미터의 이름과 값을 표시하는데, id가 파라미터의 이름이고, 숫자가 값이다. 이름과 값을 구분해주는 역할을 하는 것이 '='이다. 더 많은 정보를 전달하고 싶다면 어떻게 해야 할까? 아래와 같이 한다.
http://localhost/opentutorials.org/index.php?id=10&mode=modify
mode라는 이름의 파라미터에 modify라는 값이 부여됐다. '&'의 역할이 짐작되는가? &는 파라미터와 파라미터를 구분해주는 역할을 한다. 이 파라미터에 따라서 다른 정보를 표시하는 방법은 곧 알게 된다.
조건문
index.php는 내부적으로 사용자가 전달한 id 값에 해당하는 토픽을 데이터베이스에서 조회해야 한다. 만약 id 값이 없다면 아무것도 하지 않으면 된다. 필자가 방금 '만약'이라는 표현을 사용했다. 만약은 조건을 의미하는데, 프로그래밍의 세계에서도 조건문이있다. 조건문이란 조건에 따라서 다른 일을 하도록 하는 것이다. 아래의 코드를 보자.
if(!empty($_GET['id'])) { ]
if는 영어로 '만약에'라는 뜻이다. 프로그래밍의 세계에서도 마찬가지다. 만약에 괄호'()'안의 조건이 참(true)이면 if() 뒤에 따라오는 중괄호의 코드를 실행하고, 거짓이면 무시한다. 아래의 그림을 보면 이해하는 데 도움이 될 것이다.
$_GET['id']는 이름이 id인 파라미터의 값을 의미한다. 만약 URL이 아래와 같다면 $_GET['id']의 값은 9가 될 것이다 .
http://localhost/opentutorials/index.php?id=9
empty는 함수(function)라는 것인데, empty의 괄호 안에 값을 전달하면 그 값이 없거나 공백이면 참인 true를 거짓이면 false를 반환한다. 상황을 분해해서 생각해보자. 만약 사용자가 아래의 URL로 접근한다면 위의 로직은 아래와 같이 동작한다.
http://localhost/opentutorials/index.php?id=9
- $_GET['id'] : 9
- empty($_GET['id']) : false
- !emtpy($_GET['id'])) : true, '!'는 부정을 나타낸다. empty의 결과가 true면 false가 되고, false면 true가 된다.
- 3번의 결과가 true이기 때문에 중괄호 안의 내용이 실행된다.
아래는 id 값이 없을 때다.
http://localhost/opentutorials/index.php
- $_GET['id'] : 값이 없음
- empty($_GET['id']) : true
- !emtpy($_GET['id'])) : false
- 괄호 안의 내용이 실행되지 않는다.
자 그럼 중괄호 안의 내용으로 들어가 보자.
$_GET의 동작 방법에 대해서 더 자세히 알고 싶다면 PHP 수업을 참고하자. http://opentutorials.org/course/62/819
변수
변수는 변하는 값이라는 뜻이다. PHP에서는 변수 앞에 '$'를 사용해서 변수를 구분한다. 아래의 예를 보자. 변수 $a와 $b의 숫자가 변하면서 계산 결과도 달라진다. 언어로 치면, 일종의 대명사와 같은 역할을 한다.
<?php $a = 10; $b = 20; echo $a+$b; // 30 $a = 20; echo $a+$b; // 40 ?>
$sql = "SELECT * FROM topic WHERE id = ".$_GET['id'] 는 $sql이라는 변수에 "SELECT * FROM topic WHERE id = ".$_GET['id'] 를 할당하는 것이다. 그렇다면 URL http://localhost/opentutorials/index.php?id=9로 접근하면 $sql에는 아래와 같은 SQL이 담길 것이다.
SELECT * FROM topic WHERE id = 9
이것은 topic 테이블의 데이터 중에 id 값이 9인 데이터를 가져오라는 의미가 된다.
질의 (query)
$sql="SELECT * FROM topic WHERE id = ".$_GET['id']; $result = mysql_query($sql);
mysql_query는 그 인자(괄호 안에 들어오는 값을 인자라고 한다)를 데이터베이스 서버로 전달한다. SQL을 실행하는 역할을 하는 것이다. 현재 $sql 변수 안에는 'SELECT * FROM topic WHERE id = 9'라는 문자가 할당되어 있기 때문에 PHP 엔진은 데이터베이스 서버에게 'SELECT * FROM topic WHERE id = 9'라는 SQL문을 질의하게 된다. 데이터베이스 서버는 PHP엔진에게 그 결과를 전달한다. 그리고 아래의 명령을 통해서 그 결과를 $topic이라는 변수에 담아둔다.
$topic = mysql_fetch_assoc($result);
이 변수에는 문자나 숫자가 아니라 배열이라는 형태의 데이터가 담겨진다. 배열이란 서로 연관되어 있는 정보를 하나의 변수에 담아둔 것이라고 생각해두자.
토픽출력
$topic에는 사용자가 선택한 토픽에 대한 정보가 담겨있다. 이 정보를 본문에 출력해보자.
<article> <?php if(!empty($topic)){ ?> <h2><?=$topic['title']?></h2> <div class="description"> <?=$topic['description']?> </div> <?php } ?> </article>
<h2></h2>에는 토픽의 제목이 들어간다. $topic는 배열이라는 형태의 데이터타입이기 때문에 title에 해당하는 데이터에 접근하려면 대괄호를 사용해야 한다. 이 방식은 이미 $_GET['id']에서도 사용한 바 있다. $topic['title']는 $topic 배열에서 title에 해당하는 값을 가지고 온다. 그럼 본문을 가지고 오려면 어떻게 해야 할까? 데이터베이스 상에서 본문은 description이라는 칼럼의 이름을 가지고 있다. 따라서 $topic['description']이라고 하면 된다.
<?=...?> 는 <?php echo $topic['title']; ?> 과 같은 의미다. '<?='과 '?>' 사이의 정보를 출력하라는 의미다.
토픽리스트 출력
그럼 토픽 리스트를 출력하는 방법을 알아보자.
<nav> <ul> <?php $sql="select id,title from topic"; $result=mysql_query($sql); while($row=mysql_fetch_assoc($result)) { echo "<li><a href=\"?id={$row['id']}\">{$row['title']}</a></li>"; } ?> </ul> </nav>
$sql="select id,title from topic";
아래의 SQL문은 topic 테이블의 모든 행을 가지고 오는데 id, title 칼럼에 해당하는 데이터만을 알려달라는 의미다.
$result=mysql_query($sql); while($row=mysql_fetch_assoc($result)) { echo "<li><a href=\"?id={$row['id']}\">{$row['title']}</a></li>"; }
mysql_fetch_assoc는 배열을 반환한다. 그런데 SELECT 의 결과가 하나의 행이 아니라 여러 행인 경우는 mysql_fetch_assoc를 여러 번 호출해야 한다. mysql_fetch_assoc는 호출할 때마다 하나의 행을 리턴하고, 더 이상 리턴할 행이 없을 경우 false를 리턴한다.
while은 반복문인데, 괄호 안의 인자가 false가 될 때까지 괄호 안의 로직을 호출한다. 그리고 중괄호({}) 안의 내용을 반복적으로 실행하는데 반복될 때마다 $row의 값이 변한다. 그 결과 토픽목록을 모두 리턴할 때까지 반복적으로 아래 구문이 실행되면서 토픽의 제목과 그 토픽으로 접근할 수 있는 링크의 리스트가 만들어진다. 이 링크를 클릭하면 id 파라미터에 해당하는 토픽이 본문에 출력될 것이다.
echo "<li><a href=\"?id={$row['id']}\">{$row['title']}</a></li>";
버전관리
이번 시간에 우리는 index.php 파일을 추가했다. 이 파일을 버전관리하기 위해서는 우선 Git에게 index.php에 대한 버전관리를 지시해야 한다. 우선 상태를 점검해보자.
git status;
위와 같이 출력되는가? 조금씩 다를 수 있지만, Untracked files에 index.php가 소속되어 있는 것만 확인하면 된다. Untracked files란 버전관리 되지 않고 있는 파일들을 의미한다. 이 중 index.php를 버전관리 하려면 아래와 같이한다.
git add index.php
다시 상태를 체크해본다.
git status;
index.php 파일이 Changes to be commited의 소속으로 변경되었다. 이 파일에 대한 버전관리가 시작됐고, 커밋을 하면 저장소에 저장된다.
git commit -am 'add index.php'
이제 로컬 저장소를 원격저장소와 동기화시키자. 순서는 항상 pull(다운로드) 후에 push(업로드)다.
git pull origin master;
git push origin master;
이제 표현할 차례다. 자신의 github.com 저장소를 이 토픽의 댓글로 공유하자. 예를 들어 필자의 저장소는 아래와 같다.
https://github.com/opentutorialsorg/opentutorials4-egoing
정리
지금까지 하나의 웹서비스가 만들어지는 사이클을 직접 경험해봤다. 그 과정에서 웹서비스가 어떻게 동작하는지, 여기에 참여하는 기술들이 무엇인지에 대한 전체적인 그림을 얻었다면 바랄 게 없겠다. 다음 시간에는 여러분이 웹서비스를 실제로 서비스하는 방법에 대해서 알아본다. 우리 수업은 전체적인 윤곽을 잡는데 목적이 있다. 여기서 소개한 방법만 가지고는 실제 웹서비스를 구축하기 어렵다. 실제 웹서비스를 운영하는 방법을 알고 싶다면 PHP 기본 수업을 본 후에 CodeIgniter 수업을 보자. 정말 대단하고, 고생 많으셨어요. ^^
사족. 어떤 언어가 좋은 언어일까?
세상에는 정말 많은 언어가 있습니다. 필자도 모든 언어를 알지 못합니다. 어떤 언어는 다른 언어를 만듭니다. 이를테면 C는 많은 언어들의 아버지입니다. 많은 언어들이 C로 만들어져 있습니다. 또 어떤 언어는 대체 불가합니다. 이를테면 웹페이지를 만드는 언어는 HTML,CSS,JavaScript 밖에 없습니다. 브라우저는 이러한 언어 외에는 어떠한 언어의 문법도 이해하지 못합니다. 또 어떤 언어는 대체 가능합니다. 예를들어 데이터베이스와 웹서버를 중계해주는 역할을 하는 PHP는 Java, Python, C, C++, Ruby, Perl, JavaScript등 거의 모든 언어로 대체 가능합니다. 무엇을 선택하느냐는 여러분의 취향이거나, 여러분이 속해있는 조직의 선택입니다. 하지만 한가지 언어와 그 언어가 동작하는 방법을 이해하면 다른 언어를 배우는 것은 어렵지 않습니다. 이때의 장애물은 익숙한 언어를 놔두고 낯선 언어를 배우면서 생겨나는 조급증과 지루함 밖에 없습니다. 그렇다면 어떤 언어가 좋은 언어일까요? 정답은 없는 것 같습니다. 이를테면 PHP는 웹을 위한 언어고, 배우기가 쉽습니다. 만약 컴퓨터를 전공하지 않은 필자가 C로 웹을 시작했다면 필자는 프로그래머가 되지 못했을지도 모르겠습니다. 하지만 PHP는 좋게 말하면 관대한 언어이고, 나쁘게 말하면 어떻게 동작할지 예측이 다소 어려운 언어입니다. PHP는 개발자의 실수를 알아서 바로잡습니다. 반대로 Java나 C와 같은 엄격한 언어는 이러한 실수를 용납하지 않습니다. 이것은 장점일 수도 있고, 단점일 수도 있습니다. 필자가 언어를 공부한 것은 만들고 싶은 것이 있었기 때문입니다. 만들고자 하는 것이 있을 때 그것을 빠르게 만들 수 있는 언어가 필자의 성향에는 더 잘 맞습니다. 반대로 엔지니어링적인 미학을 추구한다면 엄격한 언어들에 호감이 갈 것입니다. 그리고 필자는 요즘에서야 엔지니어링을 수단이 아닌 그것 자체가 추구할만한 목적이라는 것을 느끼고 있습니다. 언어는 자연어건 프로그래밍 언어이건 그 언어를 사용하는 사람들이 직면한 문제와 그것을 해결한 성취와 그 사람들을 연결해주는 문화에 엑세스 할 수 있는 인터페이스면서 또 그것들을 담아내는 컨테이너입니다. 필자가 생각하는 언어의 정수는 바로 이것입니다. 이점을 기억하신다면 엔지니어링을 좀 더 풍부하게 경험하실 수 있을 것 같습니다.