러닝 PHP : PHP 입문에서 프레임워크를 활용한 실전 프로그래밍까지(PHP 7 기반)-맛보기

Page 1



러닝 PHP


| 표지 설명 | 표지 동물은 독수리(학명: Aegypius monachus )다. 독수리는 매, 수리 등과 함께 맹금류에 속한 다. 맹금류는 크게 두 종류로 구분하는데, 먹잇감을 죽이기 쉬운 신체 구조를 가진 부류와 움켜잡기 쉬운 구조를 가진 부류다. 전자는 찢거나 자르기 좋은 모양의 부리와 구부러지고 짧은 발가락, 날카로운 발톱을 지니며 후자는 찢고 쪼기 좋은 모양의 부리와 움켜쥘 수 있는 긴 발가락을 지닌다. 독수리는 전자에 해당한다. 흰꼬리 수리처럼 물고기 같은 먹이를 부드럽게 움켜쥘 수 있도록 발가락이 적응 한 종도 있다. 뉴질랜드와 남극 대륙을 제외한 전 세계 곳곳에 50여 종의 독수리가 퍼져 살고 있다. 모든 독수리 종은 지상에서 높이 떨어진 나무 꼭대기나 선반 모양의 바위에 맹금류 특 유의 둥지를 튼다. 독수리 암수 한 쌍은 매년 같은 둥지를 사용하며 푸른 잎이나 동물의 털, 잔디 등의 부드러운 재료로 둥 지에 안감을 덧대어 둥지를 보강한다. 현재까지 알려진 가장 큰 독수리 둥지의 크기는 깊이 6m, 너비 3m에 달한다. 사냥 과 농약 사용, 서식지 파괴, 먹잇감 감소 등으로 많은 종의 독수리가 현재 멸종 위기에 처해 있다.

러닝 PHP

PHP 입문에서 프레임워크를 활용한 실전 프로그래밍까지

초판 1쇄 발행 2017년 4월 1일 지은이 데이비드 스클라 / 옮긴이 정병열 / 펴낸이 김태헌 펴낸곳 한빛미디어 (주) / 주소 서울시 마포구 양화로7길 83 한빛미디어(주) IT출판부 전화 02 – 325 – 5544 / 팩스 02 – 336 – 7124 등록 1999년 6월 24일 제10 – 1779호 / ISBN 978 – 89 – 6848 – 348 –6

93000

총괄 전태호 / 책임편집 김창수 / 기획·편집 박지영 디자인 표지 김연정, 내지 여동일, 조판 이경숙 영업 김형진, 김진불, 조유미 / 마케팅 박상용, 송경석, 변지영 / 제작 박성우, 김정우 이 책에 대한 의견이나 오탈자 및 잘못된 내용에 대한 수정 정보는 한빛미디어(주)의 홈페이지나 아래 이메일로 알려주십시오. 잘못된 책은 구입하신 서점에서 교환해드립니다. 책값은 뒤표지에 표시되어 있습니다. 한빛미디어 홈페이지 www.hanbit.co.kr / 이메일 ask@hanbit.co.kr © 2017 Hanbit Media Inc.

Authorized Korean translation of the English edition of Learning PHP, ISBN 9781491933572 © 2016 David Sklar This translation is published and sold by permission of O’Reilly Media, Inc., which owns or controls all rights to publish and sell the same. 이 책의 저작권은 오라일리와 한빛미디어(주)에 있습니다. 저작권법에 의해 한국내에서 보호를 받는 저작물이므로 무단전재와 복제를 금합니다.

지금 하지 않으면 할 수 없는 일이 있습니다. 책으로 펴내고 싶은 아이디어나 원고를 메일 ( writer@hanbit.co.kr ) 로 보내주세요. 한빛미디어(주)는 여러분의 소중한 경험과 지식을 기다리고 있습니다.


PHP 7 기반

러닝 PHP


지은이·옮긴이 소개

지은이

데이비드 스클라 David Sklar

구글의 스태프 소프트웨어 엔지니어. 전에는 Ning 사에서 플랫폼, API, 샌드박스 PHP 런 타임을 개발했다. 『Learning PHP 5』( O’reilly, 2004 ), 『Essential PHP Tools』( Apress,

2004 )의 저자이며 『PHP Cookbook』(O’reilly, 2002 )의 공저자다. 식도락과 산책을 좋아하 며 가끔씩 그 두 가지를 동시에 즐기기도 한다. 예일대학교에서 컴퓨터 과학 학사를 수료했으 며 현재 뉴욕에 거주한다. 블로그(http://www.sklar.com/blog )에서 그의 번뜩이는 재기를 엿볼 수 있다.

옮긴이

정병열 cloudshadow@gmail.com

연세대학교를 졸업하고 개발자와 번역자로 활동하고 있다. 어린 시절 BASIC 언어를 통해 프 로그래밍을 처음 경험했으며 PC통신 시절에 나우누리의 프로그래밍 관련 동호회에서 활동했 다. 2000년대 초반부터 실무를 시작해 언어나 플랫폼에 관계없이 다양한 개발 프로젝트를 수 행했고, 상당 기간 데이터베이스와 리눅스 시스템 엔지니어로 근무했다. 일단 흥미를 느끼면 분야를 가리지 않고 어떻게든 파고들어 습득하고 마는 성격의 소유자로, 새로운 기술이나 책을 접하는 것을 늘 즐긴다. 역서로는 『Modern PHP』(한빛미디어, 2015 )가 있다.

4


옮긴이의 말

소프트웨어와 코딩은 이제 현대인의 기본 소양으로 자리 잡는 추세다. 미국, 중국, 영국 등을 비롯 한 많은 나라에서 이미 프로그래밍이나 컴퓨팅 관련 과목을 정규 교육과정에 포함시켰다. 국내에 서도 얼마 전 초중등 SW 교육 활성화 계획을 발표했으며 단계적인 교육과정 개편 계획을 수립하 는 중이다. 마이크로소프트, 페이스북 등의 IT 대기업들도 코딩 교육의 중요성을 강조하며 실제 로 교육 서비스를 제공하기 시작했다. 온라인 교육 커리큘럼도 많다. 초급 코딩부터 알고리즘 훈 련에 이르기까지 원하는 과정을 얼마든지 무료로 배울 수 있다. 이런 분위기에 힘입어 프로그래 밍을 배우려는 사람도 크게 늘었다. 직업이나 전공을 떠나 순수하게 재미와 흥미를 느끼는 사람 도 있고 사고력과 논리력 증진, 트렌드 따라잡기의 일환으로 코딩에 관심을 보이는 사람도 있다. 프로그래밍을 처음 시작하는 이들이 가장 먼저 느끼는 고민 중 하나는 어떤 언어를 선택해야 하는가다. 폴리글랏이라던가 풀스택 같은 용어가 이제는 낯설지 않겠지만, 다양한 언어를 두루 섭렵할 만큼 시간과 자원이 넉넉한 사람은 많지 않다. 인터넷에는 정보가 흘러넘쳐 오히려 혼 란스럽다. 주변의 개발자 친구들에게 물어보면 객체 지향이 어떻다느니 함수형 언어가 어떻다 느니 하는 도무지 알 수 없는 얘기만 돌아오는 데다, 추천하는 언어도 제각각이다. 최근 주목받 는 신생 언어를 배우라는 사람도 있고 긴 역사와 높은 점유율을 자랑하는 전통의 강호를 먼저 정복해야 한다는 사람도 있다.

PHP는 이제 막 프로그래밍을 배우기 시작하려는 사람에게 무난히 권장할 수 있는 언어다. PHP가 웹 서비스 개발 분야에서 보인 높은 점유율과 빠른 보급 속도는 PHP의 탁월한 접근성 을 입증한다. PHP는 스크립트 언어의 장점을 지닌 동시에 풍부한 내장 라이브러리를 보유하 고 있어 간단한 코딩만으로 원하는 결과를 쉽게 얻을 수 있다. 또한 PHP는 언어 구조나 기능 적인 면에서 웹 플랫폼에 최적화된 언어다. 다양한 웹 서버에 쉽게 탑재할 수 있으며 설치 자체 도 쉬워서 서버와 클라이언트 양쪽에 모두 사용할 수 있다. 자고 나면 신기술이 등장하는 최첨 단 분야인 웹 플랫폼에서, PHP는 여전히 가장 많은 선택을 받는 웹 개발 언어 중 하나다. 오랜 산고 끝에, 2015년 말 PHP 7의 정식 버전이 발표됐다. 개발 중이던 6 버전을 과감히 건 너뛸 만큼 많은 고민과 도전을 반영한 메이저 업데이트다. 아래로는 하위 호환성을 최대한 유

5


지하면서 그간 비판받았던 언어적 단점을 보완하고, 위로는 비약적인 성능 향상과 신기능 도 입을 통해 웹 서비스 개발 언어로서 지속적인 저변 확대를 꾀했다. 무엇보다 일부 언어의 사례 처럼 지나치게 급격한 변화로 인해 버전 이행이 지체되거나 기존 버전이 완전히 방치되는 일이 없도록 고민한 흔적도 엿볼 수 있다. 초심자에게는 내장 웹 서버와 REPL이 큰 도움이 될 것이 다. 이 둘은 ‘간단히 코딩하고 즉시 확인할 수 있는’ 프로그래밍 도우미다. 그간 PHP의 약점으 로 지적되던 타입 지원을 강화하고 일관되지 않은 예외 클래스 구조를 정비한 점은 프로그래밍 숙련자와 초심자 모두에게 의미가 큰 변화다. 『러닝 PHP』는 입문서인 동시에 실용서다. API 레퍼런스를 A부터 Z까지 빼곡히 수록하거나, 금세 읽다 지칠 지루한 이론을 늘어놓지 않는다. 그저 술술 읽고 따라 해보는 것만으로 자연 스럽게 프로그래밍에 발을 들이게 되고 어느새 웹사이트의 주요 기능을 구현할 수 있을 정도 로 익숙해진다. 패키지 관리, 명령행 스크립트 실행 등 PHP의 다양한 활용 방법도 빼놓지 않 고 다룬다. 프로그래밍 자체에 처음 도전하는 문외한뿐만 아니라 PHP에 관심은 있었지만 쓸 만한 입문서를 찾지 못했던 프로그래머에게도 마찬가지로 도움이 될 내용이다. PHP를 이용한 개발 방식이 어디까지 고도화될 수 있는가에 초점을 맞춘 책이 『모던 PHP』라면, 『러닝 PHP』는

PHP가 얼마나 쉽고 친근한 언어인지 극단적으로 보여주는 도서다. 이 책을 출간하기로 결정하고 번역을 제안한 한빛미디어와 최현우 차장님께 감사한다. 국내에 출간된 PHP 입문서는 집필, 번역 서적을 통틀어도 종류가 그리 많지 않다. 물론 고급 개발 기 술에 관한 관심과 수요는 중요하지만, 한편으로는 프로그래밍이 기본 소양으로 인정받기 시작 한 지금과 같은 때에 『러닝 PHP』 같은 입문서의 역할은 이전보다 더욱 중요해졌다. 번역을 진 행하며 종종 초심자 시절의 기억이 되살아나곤 했다. 시간이 흘러 이제 내가 PHP를 시작하는 누군가에게 도움을 준다는 생각에, 개인적으로도 매우 의미 있는 시간이었다. 작업 기간 내내 친절하게 배려해주신 박지영 과장님께 감사의 인사를 드린다. 유독 잔 실수가 많았던 초고의 몇몇 부분을 떠올리면 미안한 마음이 앞선다. 끝으로, 얼마 전 평생의 반려자가 되어준 사랑하 는 아내에게 감사를 전한다. 옮긴이_ 정병열

6


추천의 글

모던 PHP 유저 그룹 내에서 종종 나오는 이야기 중의 하나는, 새로 PHP를 배우려는 사람들에 게 딱히 권해줄 책이 없다는 것이었습니다. PHP 5.3 이 등장한 이후 PHP의 새로운 르네상스 시대가 왔다고 할 만큼 큰 변화가 있었던 반면, 국내에는 현대적인 PHP 입문서가 많이 소개되 지 않아 아쉬웠습니다. 이 책을 받고 가장 먼저 확인한 부분은 바로 데이터베이스를 다루는 장 이었습니다. 만약 PDO를 기본으로 설명한다면 입문자에게 권할 만하다고 생각했는데, 역시 이 책은 기대를 저버리지 않았습니다. 러닝 PHP는 현대적인 PHP의 기초 사용법을 꼼꼼하고 친절하게 안내해줍니다. 특히 너무 쉽지도 너무 어렵지도 않은 연습문제를 매 단원 제공하여 학습한 바를 확인할 수 있는 점이 인상적이었습니다. 국내에 출간되는 PHP 도서 종수가 적다는 점 외에도, 최근 출간된 극히 일부의 도서를 제외하 고는 기초 이상으로 성장할 수 있도록 안내해주는 책이 없는 점이 매우 안타깝던 참입니다. 이 책은 적지 않은 분량을 할애하여 다양한 소프트웨어 개발기법과 고급 기술을 소개합니다. 주요 대상 독자는 프로그래밍에 흥미가 있고 지적 능력도 충분하지만, 굳이 기술적으로 전문가가 될 필요는 없는 이들입니다. 하지만 초급 이상으로 성장하고자 하는 입문자에게도 매우 훌륭한 입 문서가 될 것입니다. 이현석_ 모던 PHP 유저그룹 운영자 & 업투데이북스 대표

7


정말 반가운 책이다.

PHP는 지속해 발전하며 전 세계 인터넷 산업에서 큰 비중을 차지하는 프로그래밍 언어다. 그 런데도 국내에서는 PHP 발전 속도에 맞는 책을 찾아보기 어려웠다. 특히 PHP에 입문하려 는 분들께 추천할 만한 기본서가 없다는 점이 가장 아쉬웠다. 그러던 중 최신 입문서인 『러닝

PHP』가 출간된다는 것은 올해를 시작하며 들은 가장 반가운 소식이다.

웹 개발을 더욱 쉽게 이해할 수 있는 책이다. 웹은 특정 프로그래밍 언어에 국한된 기술이 아니다. 만약 웹 개발에 관심 있고 처음 시작점으 로 PHP를 선택한 것이라면 정말 괜찮은 판단이었다고 말씀드리고 싶다. 무언가를 처음 배우 는 과정은 절대 쉽지 않다. 그럴 때일수록 더 쉽게 이해할 방법을 택하는 쪽이 합리적이며 효과 적이다.

PHP는 웹 개발에 최적화된 프로그래밍 언어로, 이 책 또한 웹 개발을 위한 지식과 기술을 다 룬다. 단지 그 수단으로 PHP를 사용할 뿐이다. 가장 먼저 해야 할 일은 웹을 이해하는 것이며, 훗날 웹 개발을 위해 다른 프로그래밍 언어를 선택해도 무방하다. 그건 당연히 각자의 몫이다.

오랫동안 곁에 두고 싶은 책이다. 기본서이면서도 지루하지 않게, 쉬운 듯하면서도 자세하게 PHP를 설명하므로 웹 개발을 더 재미있게 시작할 수 있도록 도와준다. 책을 다 읽은 후에는 책장에 꽂아두고 언제든 편한 마음 으로 꺼내어 빠르게 읽어보기를 권한다. 마치 봤던 영화를 다시 볼 때 새로운 장면이 보이는 것 과 같은 묘미를 느낄 수 있을 것이다. 용영환_ PHP Korea 리더 & 서울옥션블루 CTO

8


이 책에 대하여

정적인 웹사이트는 따분하다. 사용자의 흥미를 유발하려면 동적인 웹사이트를 구축해야 한다. 동적인 웹사이트란 다시 말해 콘텐트가 변화하는 웹사이트다. 어떤 회사가 보유한 1,000개의 상품을 한 번에 보여주는 HTML 웹 페이지가 있다고 해보자. 전체 상품의 제품명, 사진, 설명 이 모두 나열된 이 거대한 정적 웹 페이지는 실용성이 떨어질 뿐만 아니라 불러오는 데도 까마 득한 시간이 걸린다. 동적인 상품 목록은 사용자가 상품을 검색하고 추릴 수 있으며, 원하는 가 격대와 종류에 맞는 상품을 즐겨찾기 목록에 저장할 수도 있다. 편리하고 빠른 동시에 판매로 이어질 가능성도 높다.

PHP 프로그래밍 언어를 이용하면 동적 웹사이트를 쉽게 제작할 수 있다. 여러분이 만들어내 고자 하는 짜릿한 상호작용-상품 카탈로그, 블로그, 사진집, 일정표 등–이 무엇이든, PHP로 모두 구현할 수 있다. 이 책을 읽고 나면 여러분도 동적 웹사이트를 마음껏 만들 수 있게 될 것 이다.

이 책의 대상 독자 이 책은 다음과 같이 다양한 사람들에게 유용하다. 취미로 자신이나 가족, 비영리 단체 등이 사용할 인터랙티브 웹사이트를 제작하려는 이

●●

ISP나 호스팅 업체에서 제공하는 PHP 환경을 이용해 웹사이트를 구축하고자 하는 제작자

●●

드루팔, 워드프레스, 미디어위키와 같은 유명 PHP 소프트웨어의 플러그인 혹은 확장 기능을 만들고자 하는

●●

개발자 또는 디자이너 동료 개발자와 더 유연하게 소통하기 원하는 웹 디자이너

●●

자신의 클라이언트 사이드 코드를 보완해줄 서버 사이드 프로그램을 직접 만들고자 하는 자바스크립트 숙련자

●●

PHP를 이용해 작업 속도를 높이고자 하는 펄, 파이썬, 루비, 기타 언어 개발자

●●

인터랙티브 웹사이트 구축에 가장 많이 사용되는 프로그래밍 언어를 간단하면서 쉬운 설명으로 배우고 싶은 이

●●

PHP는 학습 곡선이 완만하고 구문도 이해하기 쉬워서 기술적 전문가가 아닌 다른 분야의 웹

9


전문가가 입문하기에 이상적인 언어다. 이 책의 대상 독자는 프로그래밍에 흥미가 있고 지적 능력도 충분하지만, 굳이 기술적으로 전문가가 될 필요는 없는 이들이다. 다른 언어에 이미 익 숙하며 PHP를 배워보고자 하는 프로그래머도 여기에 포함된다. 프로그래밍에 완전히 문외한이고 인터랙티브 웹사이트 제작에 처음 발을 들이는 사람은 이 책 을 처음부터 차근차근 읽어야 한다. 이 책의 초반은 PHP 문법과 PHP에 적용된 기본적인 컴퓨 터 프로그래밍 개념을 친절하게 안내한다. 다른 프로그래밍 언어에 익숙하지만 PHP 프로젝트는 처음인 사람들은 2장부터 바로 시작해도 좋다. 특정 문법이나 PHP의 기본적인 사항에 대한 궁금증이 생길 때마다 1장으로 돌아가 확 인하라. 이 책을 읽기에 앞서, 기본적인 컴퓨터 활용능력(키보드 사용, 파일 관리, 웹서핑 등) 가운데 여러분에게 이미 익숙하리라 전제하는 분야는 단 하나, HTML이다. 반드시 HTML 전문가일 필요는 없지만 <html >, <head >, <body >, <p >, <a >, <br > 등 웹 페이지를 구성하는 기본 적인 HTML 태그에 익숙해야 한다. HTML과 친하지 않다면 엘리자베스 롭슨과 에릭 프리먼 이 쓴 『Head First HTML and CSS』(한빛미디어, 2013 )를 참고하기 바란다.

이 책의 구성 이 책은 첫 장부터 시작해 차례차례 순서대로 진행하도록 구성했다. 각 장의 내용은 대체로 이 전 장의 내용과 연결된다. 2장부터 13장까지는 각 장의 내용을 이해하는 데 도움이 될만한 연 습문제를 함께 제공한다.

1부 처음 만나는 PHP 1장은 PHP에 대한 일반적인 배경 지식과 더불어 PHP가 웹 브라우저 및 웹 서버와 상호작 용하는 방식을 설명한다. 또한 PHP 프로그램의 외형과 작동 방식의 개념을 정립하고자 예시

10


PHP 프로그램을 직접 실행한다. 프로그래밍이나 동적 웹사이트 구축 경험이 부족한 초심자는 반드시 1장을 읽어야 한다.

2부 PHP 기본기 2장부터 6장까지는 PHP 기초 교육에 해당한다. 위대한 문학 작품을 쓰려면 먼저 사소한 문법 이나 어휘부터 올바르게 배워야 한다. 2부의 5개 장이 그러한 역할을 한다. 위대한 문학 작품 까지는 아니더라도 작은 프로그램을 올바르게 만들 수 있는 충분한 PHP 문법과 어휘를 배우 게 될 터이니 너무 겁먹을 필요는 없다.

2장은 문자열이나 숫자 같은 다양한 데이터를 다루는 방법을 보여준다. PHP 프로그램이 생성 하는 웹 페이지는 결국 거대한 문자열이라는 점에서 이 장은 매우 중요하다.

3장은 프로그램으로 판단과 결정을 내릴 수 있는 PHP 구문에 관해 설명한다. 이러한 구문이 야말로 웹사이트의 ‘동적’인 부분을 담당하는 핵심이다. 3장에서 설명하는 개념을 활용하면 사 용자와 상호작용하는 기능, 이를테면 전체 상품 중 가격 조건에 맞는 상품만 선택적으로 보여 주는 기능을 구현할 수 있다.

4장에서는 배열을 소개한다. 배열은 개별 숫자나 문자열 조각의 집합이다. 배열은 웹 폼을 통 해 제출된 매개변수, 데이터베이스에서 가져온 자료 등을 처리할 때 사용하며, 그 외에도 빈번 하게 수행되는 작업 대부분이 배열 처리를 수반한다. 복잡한 프로그램을 작성하다 보면 비슷한 작업을 반복적으로 수행하는 경우가 많다. 5장에서 논의하게 될 함수 구조를 이용하면, 프로그램 일부를 반복적으로 재사용할 수 있다.

6장은 데이터와 로직을 객체를 통해 조합하는 방법을 보여준다. 객체란 재사용할 수 있는 코드 뭉치로, 프로그램을 구조화할 수 있는 도구다. 또한 이미 존재하는 PHP 애드온이나 라이브러 리도 객체를 통해 자신의 코드에 통합할 수 있다.

11


3부 동적 웹사이트 구축 실전 7장부터 11장까지 5개 장은 사용자와의 상호작용, 정보 저장, 다른 사이트와의 상호작용 등 동 적 웹사이트를 구축하는 데 필요한 필수적인 작업을 설명한다.

7장은 사용자가 웹사이트와 상호작용하는 주된 방식인 웹 폼이 어떻게 동작하는지 자세하게 설명한다. 뒤이어 8장에서는 데이터베이스를 다룬다. 데이터베이스는 상품 정보나 일정 정보 등 웹사이트가 제공하는 정보의 원천 데이터를 담고 있다. 이 장은 PHP 프로그램을 이용해 데 이터베이스와 대화하는 방법을 보여준다. 8장에서 소개하는 기술을 활용하면 웹사이트에 개인 화된 기능을 추가할 수 있다. 개인화된 기능이란 민감한 정보를 허가된 사용자에게만 보여주거 나, 마지막으로 로그인한 이후 등록된 글의 개수를 알려주는 등의 기능을 말한다. 데이터베이스뿐만 아니라 파일을 이용해 데이터를 저장해야 할 때도 있다. 9장은 PHP 프로그 램을 이용해 파일을 읽고 쓰는 방법을 설명한다. 10장은 사용자와 지속해서 정보를 주고받는 방법을 설명한다. 쿠키를 이용해 일회성 데이터를 처리하고, 세션 데이터를 이용해 사용자의 로그인 상태나 쇼핑 장바구니 목록 등을 지속해서 저장하는 방법을 배운다. 이 단계의 마지막인 11장은 PHP 프로그램이 다른 웹사이트나 웹 서비스와 상호작용하는 방법 을 자세히 알아본다. 이 장에서 배운 내용을 이용해 다른 웹 페이지나 웹 API의 내용을 가져와 자신의 프로그램에서 사용할 수 있다. 역으로, 외부 클라이언트에 응답하는 API를 PHP로 직 접 만들 수도 있다.

4부 소프트웨어 개발 기법과 PHP 12장부터 14장까지는 프로그램의 기능을 설명하는 대신, 여러분이 더 나은 프로그래머가 되려 면 알아야 할 것들을 논의한다.

12장은 프로그램의 오류를 찾아 고치는 과정인 디버깅에 관해 설명한다. 13장은 테스트를 작 성하고 프로그램의 각기 다른 부분을 검사하는 방법을 보여준다. 자신의 프로그램이 기대한 대

12


로 작동하는지 테스트를 통해 확인할 수 있다. 마지막으로 14장은 다른 개발자와 프로젝트를 수행할 때 알아두면 좋은 소프트웨어 공학 기법 을 설명한다. PHP뿐만 아니라 다른 프로그래밍 언어에도 해당하는 내용이다.

5부 고급 기술과 프레임워크 이 책의 마지막 부분은 일반적인 작업 몇 가지와 처리 방법을 알아본다. 이들 작업은 PHP의 기본 구조 혹은 정보 저장 방법 등에 비해 중요도가 높지 않지만, PHP를 다루다 보면 자연히 많은 시간을 들이게 된다. 5부는 이런 작업을 처리하는 기본기를 가르친다.

15장은 PHP의 강력한 날짜 시간 처리 기능을 종합해 보여준다. 16장은 패키지 관리를 설명한 다. 패키지 관리자를 활용하면 다른 사람이 만든 우수한 라이브러리를 아주 쉽게 자신의 코드 로 가져올 수 있다. 17장은 PHP 프로그램으로 이메일 메시지를 전송하는 방법을 설명한다.

18장은 PHP 웹 애플리케이션 프레임워크 중 가장 유명한 세 가지를 소개한다. 프레임워크를 활용하면 공통적인 기능을 직접 구현할 필요 없이 단숨에 프로젝트를 시작할 수 있다. 19장은 웹 서버를 거치지 않고 명령행에서 PHP를 실행하는 방법을 살펴보는데, 이를 이용해 간단한 유틸리티를 만들거나 짧은 프로그램을 테스트할 수 있다. 마지막으로 20장은 서로 다른 언어 나 문자집합으로 이루어진 문자열을 PHP로 적절히 다루는 방법과 기법들을 설명한다.

6부 부록: 설치와 연습문제 해답 부록으로 제공되는 두 개장은 추가적인 자료를 제공한다. PHP 프로그램을 움직이려면 자신 의 컴퓨터에 PHP 엔진을 설치해야 하거나 PHP를 지원하는 웹 호스팅을 사용해야 한다. 부록

A는 윈도우, macOS, 리눅스 환경에서 PHP를 설치하고 실행하는 방법을 보여준다. 부록 B 는 이 책에 나온 모든 예제의 해답을 제공한다. 직접 풀어보기 전까지는 훔쳐보지 말아야 할 곳 이다.

13


이 책이 다루지 않는 내용 이 책의 제한된 분량으로는 안타깝게도 PHP와 관련된 내용을 모두 다룰 수 없다. 이 책의 주 된 목표는 PHP와 컴퓨터 프로그래밍의 기본 중 핵심적인 일부를 소개하는 것이다.

PHP 7 의 신기능에 흥미가 있는 PHP 프로그래머는 http ://php .net /manual /kr /

migration70.php 에서 PHP 7의 신기능과 차이점을 살펴볼 수 있다. 또한 사이트포인트 (http://bit.ly/skvorc-php7 )도 다양한 PHP 7 참고 자료를 제공한다.

참고 자료 온라인으로 제공되는 PHP 매뉴얼(http://www.php.net/manual )은 PHP의 방대한 함수 라이브러리를 탐색할 수 있는 아주 좋은 자료다. 사용자들이 직접 작성한 댓글을 통해 유용한 조언과 예시 코드를 얻을 수 있다. PHP 설치, 프로그래밍, 확장, 기타 다양한 주제들을 다루는

PHP 메일링 리스트도 많다. php.net에서 이러한 메일링 리스트에 대해 알아보고 가입할 수 있다. 또한 PHP 프레젠테이션 시스템 저장소(http://talks.php.net )도 좋은 참고자료다. 여 러 콘퍼런스에서 발표한 프레젠테이션을 모아놓은 곳이다.

PHP를 더 깊이 있게 배우고 싶다면 PHP The Right Way (http://www.phptherightway.

com )를 추천한다. 훌륭한 자료이며 이미 다른 언어에 익숙한 사람들에게 특히 유용하다. 이 책의 내용을 익히고 난 뒤 다음 책들을 읽으면 PHP와 SQL 등의 학습에 도움이 될 것이다. 『Head First PHP & MySQL』(한빛미디어, 2010 ) : PHP와 MySQL을 이용해 웹사이트를 신속하게 개

●●

발할 수 있도록 돕는 지침서. 실용적인 다양한 연습문제를 통해 웹 개발에 필요한 광범위한 지식을 얻을 수 있다. 『Modern PHP』(한빛미디어, 2015 ) : 앞서 추천한 PHP The Right Way 사이트의 내용을 깔끔히 갈무리

●●

한 책이다. PHP의 문법이나 사용방법을 설명하지 않는 대신 PHP의 세련된 기능과 모범 사례들을 제시한다. 또한 PHP 웹 애플리케이션의 배포, 테스팅, 프로파일링까지 다룬다. 이 책을 읽고 나면 한층 더 일관적이고 품 질 높은 PHP 프로그램을 작성할 수 있을 것이다.

14


『SQL 첫걸음』(한빛미디어, 2015 ), 『SQL 레벨업』(한빛미디어, 2016 ) : 기초적인 SQL 문법부터 성능 최적

●●

화 등의 고급 기법까지 이어서 배울 수 있는 시리즈다. MySQL, PostgreSQL 등의 데이터베이스 프로그램 설치 방법도 함께 안내한다. 『이것이 MySQL이다』(한빛미디어) : 실무 환경에서 이뤄지는 MySQL의 실제 사용 사례를 실습을 통해 체

●●

험할 수 있는 실용서다. 81가지 실습 예제와 함께 저자 직강의 무료 동영상 강의를 들으며 과외 수업을 받듯이

MySQL을 배울 수 있다.

사용한 PHP 버전 이 책의 예제는 PHP 7.0.0을 기준으로 작성됐으며 출판 당시 PHP의 최신 버전인 7.0.16에서 테스트했다. PHP 5.4.0 이후에 추가된 기능을 참조하거나 사용할 때는 해당 기능이 어느 버전 에서 추가됐는지 언급할 것이다.

코드 예제 내려받기 원서의 코드 예제는 깃허브 저장소(https://github.com/oreillymedia/Learning _PHP )에 서 내려받을 수 있다. 번역서 내용에 맞게 현지화한 코드 예제는 다음 깃허브 저장소에서 확인 할 수 있다(책 뒤표지에는 단축 URL로 표기했다). https ://github.com/cloud -shadow/Hanbit_Learning_PHP

●●

PHP를 처음 시작하는 이들은 기왕이면 이 책의 예제 프로그램을 직접 따라 해보는 것이 좋다. 하지만 굳이 그럴 필요가 없는 이들은 전체 코드를 내려받아 실행해도 된다.

15


CONTENTS

PART

지은이·옮긴이 소개 ....................................................................................................

4

옮긴이의 말 ...............................................................................................................

5

추천의 글 ..................................................................................................................

7

이 책에 대하여 ..........................................................................................................

9

I 처음 만나는 PHP

CHAPTER

1 오리엔테이션과 첫걸음 1.1 웹 세상에서 PHP의 역할 ............................................................................................. 29 1.2 PHP가 뭐가 그렇게 대단해? ........................................................................................ 33 1.2.1 PHP는 무료다 ............................................................................................... 33 1.2.2 PHP는 자유롭다 .............................................................................................. 34 1.2.3 PHP는 크로스 플랫폼을 지원한다 ........................................................................ 34 1.2.4 PHP는 광범위하게 사용된다 .............................................................................. 34 1.2.5 PHP는 복잡성을 해소한다 ................................................................................. 35 1.2.6 PHP는 웹 프로그래밍을 위해 만들어졌다 .............................................................. 35

1.3 PHP 따라 하기 .......................................................................................................... 35 1.4 PHP 프로그램의 기본 규칙 .......................................................................................... 42 1.4.1 시작 태그와 종료 태그 ........................................................................................ 43 1.4.2 화이트스페이스와 대소문자 구분 .......................................................................... 44 1.4.3 주석 ............................................................................................................... 46

1.5 마치며 ...................................................................................................................... 48

16


PART

II PHP 기본기

CHAPTER

2 데이터: 텍스트와 숫자 다루기 2.1 텍스트 ...................................................................................................................... 52 2.1.1 텍스트 문자열 정의 . .......................................................................................... 52 2.1.2 텍스트 다루기 .................................................................................................. 57

2.2 숫자 ......................................................................................................................... 65 2.2.1 다양한 수 다루기 .............................................................................................. 66 2.2.2 산술 연산자 ..................................................................................................... 66

2.3 변수 ......................................................................................................................... 68 2.3.1 변수의 연산 ..................................................................................................... 70 2.3.2 문자열 내부에 변수 넣기 ..................................................................................... 72

2.4 마치며 ...................................................................................................................... 74 2.5 연습문제 ................................................................................................................... 75

CHAPTER

3 로직: 조건 판단과 반복 수행 3.1 참과 거짓 ................................................................................................................. 78 3.2 조건 판단 .................................................................................................................. 79 3.3 복잡한 조건 설계 ........................................................................................................ 82 3.4 반복 실행 .................................................................................................................. 91 3.5 마치며 ...................................................................................................................... 95 3.6 연습문제 ................................................................................................................... 95

17


CONTENTS

CHAPTER

4 데이터 집합: 배열 다루기 4.1 배열 기초 .................................................................................................................. 98 4.1.1 배열 생성 ........................................................................................................ 98 4.1.2 적절한 배열명 ................................................................................................ 100 4.1.3 숫자 키 배열 .................................................................................................. 101 4.1.4 배열 크기 구하기 ............................................................................................ 103

4.2 배열 원소 순회 ......................................................................................................... 103 4.3 배열 수정 ................................................................................................................ 111 4.4 배열 정렬 ................................................................................................................ 115 4.5 다차원 배열 ............................................................................................................. 120 4.6 마치며 .................................................................................................................... 124 4.7 연습문제 ................................................................................................................. 125

CHAPTER

5 논리 집합: 함수와 파일 5.1 함수 선언과 호출 ...................................................................................................... 128 5.2 함수의 인수 전달 ...................................................................................................... 130 5.3 함수의 반환값 ......................................................................................................... 135 5.4 변수 영역 ................................................................................................................ 140 5.5 인수와 반환값 제한 ................................................................................................... 145 5.6 다른 파일의 코드 실행하기 ......................................................................................... 148 5.7 마치며 .................................................................................................................... 150 5.8 연습문제 ................................................................................................................. 151

18


CHAPTER

6 데이터와 로직: 객체 다루기 6.1 객체 기본 ................................................................................................................ 154 6.2 생성자 .................................................................................................................... 157 6.3 예외로 문제점 확인하기 ............................................................................................. 159 6.4 객체 확장 ................................................................................................................ 162 6.5 속성과 메서드 가시성 ................................................................................................ 165 6.6 네임스페이스 ........................................................................................................... 167 6.7 마치며 .................................................................................................................... 169 6.8 연습문제 ................................................................................................................. 170

PART

III 동적 웹사이트 구축 실전

CHAPTER

7 사용자와 정보 주고받기: 웹 폼 제작 7.1 유용한 서버 변수 ...................................................................................................... 177 7.2 폼 매개변수 접근 ...................................................................................................... 178 7.3 폼 처리 함수 ............................................................................................................ 181 7.4 데이터 검증 ............................................................................................................. 184 7.4.1 필수 요소 ...................................................................................................... 186 7.4.2 숫자와 문자열 요소 . ........................................................................................ 186 7.4.3 수치 범위 ...................................................................................................... 190 7.4.4 이메일 주소 ................................................................................................... 192 7.4.5 <select> 메뉴 ................................................................................................. 192 7.4.6 HTML과 자바스크립트 .................................................................................... 196 7.4.7 구문 너머 ...................................................................................................... 199

19


CONTENTS

7.5 기본값 표시 ............................................................................................................. 199 7.6 전부 합치기 ............................................................................................................. 203 7.7 마치며 .................................................................................................................... 213 7.8 연습문제 ................................................................................................................. 213

CHAPTER

8 정보 저장: 데이터베이스 8.1 데이터 조직 ............................................................................................................. 217 8.2 데이터베이스 프로그램 접속 ....................................................................................... 218 8.3 테이블 생성 ............................................................................................................. 221 8.4 데이터 추가하기 ....................................................................................................... 223 8.5 데이터 삽입 보안 ...................................................................................................... 231 8.6 데이터 입력 폼 프로그램 ............................................................................................ 234 8.7 데이터 가져오기 ....................................................................................................... 238 8.8 반환 결과 형식 변경 .................................................................................................. 243 8.9 데이터 요청 보안 ...................................................................................................... 245 8.10 데이터 검색 폼 ...................................................................................................... 248 8.11 마치며 ................................................................................................................. 254 8.12 연습문제 ............................................................................................................... 255

CHAPTER

9 파일 다루기 9.1 파일 접근 권한 ......................................................................................................... 258 9.2 전체 파일 읽고 쓰기 .................................................................................................. 259 9.2.1 파일 읽기 ...................................................................................................... 259 9.2.2 파일 쓰기 ...................................................................................................... 260

9.3 파일 일부분 읽고 쓰기 ............................................................................................... 261

20


9.4 CSV 파일 다루기 ..................................................................................................... 265 9.5 파일 권한 확인 ......................................................................................................... 268 9.6 오류 검사 ................................................................................................................ 270 9.7 외부에서 입력받은 파일명 처리 ................................................................................... 273 9.8 마치며 .................................................................................................................... 275 9.9 연습문제 ................................................................................................................. 276

CHAPTER

10 사용자 추적: 쿠키와 세션 10.1 쿠키 다루기 ........................................................................................................... 278 10.2 세션 활성화 ........................................................................................................... 284 10.3 정보 저장과 확인 .................................................................................................... 285 10.4 세션 설정 .............................................................................................................. 290 10.5 로그인과 사용자 식별 .............................................................................................. 292 10.6 setcookie()와 session_start()가 페이지 맨 위에 있어야 하는 이유 ................................ 299 10.7 마치며 ................................................................................................................. 301 10.8 연습문제 ............................................................................................................... 302

CHAPTER

11 다른 웹사이트와 통신하기 11.1 파일 함수를 이용한 URL 간편 접근 ........................................................................... 304 11.2 cURL을 이용한 URL 상세 접근 ............................................................................... 310 11.2.1 GET으로 URL 가져오기 ................................................................................ 310 11.2.2 POST로 URL 가져오기 ................................................................................ 313 11.2.3 쿠키 사용 .................................................................................................... 314 11.2.4 HTTPS URL 가져오기 .................................................................................. 318

11.3 API 요청 수신 ....................................................................................................... 319

21


CONTENTS

11.4 마치며 ................................................................................................................. 323 11.5 연습문제 ............................................................................................................... 324

PART

IV 소프트웨어 개발 기법과 PHP

CHAPTER

12 디버깅 12.1 오류 발생 위치 제어 ................................................................................................ 327 12.2 구문 오류 수정 ....................................................................................................... 329 12.3 프로그램 데이터 조사 .............................................................................................. 333 12.3.1 디버그 출력 ................................................................................................ 334 12.3.2 디버거 사용 ................................................................................................. 338

12.4 전역적 예외 처리 .................................................................................................... 343 12.5 마치며 ................................................................................................................. 345 12.6 연습문제 ............................................................................................................... 345

CHAPTER

13 테스팅: 프로그램 검증 13.1 PHP유닛 설치 ...................................................................................................... 348 13.2 테스트 작성 ........................................................................................................... 349 13.3 테스트 고립시키기 .................................................................................................. 353 13.4 테스트 주도 개발 .................................................................................................... 357 13.5 참고 자료 .............................................................................................................. 359 13.6 마치며 ................................................................................................................. 360 13.7 연습문제 ............................................................................................................... 361

22


CHAPTER

14 알아두면 좋은 소프트웨어 공학 기법 14.1 소스 관리 .............................................................................................................. 364 14.2 이슈 추적 .............................................................................................................. 365 14.3 환경 구성과 배포 .................................................................................................... 366 14.4 규모 측정 .............................................................................................................. 368 14.5 마치며 ................................................................................................................. 369

PART

V 고급 기술과 프레임워크

CHAPTER

15 날짜와 시간 처리 15.1 날짜와 시간 출력 .................................................................................................... 374 15.2 날짜와 시간 표현 .................................................................................................... 376 15.3 날짜와 시간 계산 .................................................................................................... 379 15.4 시간대 설정 .......................................................................................................... 381 15.5 마치며 ................................................................................................................. 382

CHAPTER

16 패키지 관리 16.1 컴포저 설치 ........................................................................................................... 383 16.2 패키지 추가 ........................................................................................................... 384 16.3 패키지 검색 ........................................................................................................... 386 16.4 더 많은 정보 .......................................................................................................... 388 16.5 마치며 ................................................................................................................. 388

23


CONTENTS

CHAPTER

17 메일 보내기 17.1 스위프트 메일러 ..................................................................................................... 389 17.2 마치며 ................................................................................................................. 392

CHAPTER

18 프레임워크 18.1 라라벨 ................................................................................................................. 394 18.2 심포니 ................................................................................................................. 396 18.3 젠드 .................................................................................................................... 398 18.4 마치며 ................................................................................................................. 401

CHAPTER

19 명령행 PHP 19.1 명령행 PHP 프로그램 작성 ...................................................................................... 404 19.2 PHP 내장 웹 서버 ................................................................................................. 406 19.3 PHP REPL .......................................................................................................... 406 19.4 마치며 ................................................................................................................. 408

CHAPTER

20 국제화와 지역화 20.1 텍스트 처리 ........................................................................................................... 410 20.2 정렬과 비교 ........................................................................................................... 412 20.3 출력 지역화 ........................................................................................................... 414 20.4 마치며 ................................................................................................................. 416

24


PART

VI 부록: 설치와 연습문제 해답

APPENDIX

A PHP 엔진 설치와 설정 A.1 웹 호스팅을 통해 PHP 실행하기 ................................................................................ 419 A.2 PHP 엔진 설치 ....................................................................................................... 420 A.2.1 macOS ....................................................................................................... 420 A.2.2 리눅스 .......................................................................................................... 421 A.2.3 윈도우 .......................................................................................................... 422

A.3 PHP 설정 지시자 .................................................................................................... 423 A.4 마치며 ................................................................................................................... 429

APPENDIX

B 연습문제 해답

찾아보기 ........................................................................................................................

431

486

25



Part

I

처음 만나는 PHP

27


Part I

처음 만나는 PHP

1장 오리엔테이션과 첫걸음

28


CHAPTER

1

오리엔테이션과 첫걸음

PHP로 프로그래밍하는 이유는 다양하다. 누군가는 소규모 웹사이트를 만들고 상호작용할 수 있는 요소를 넣기 위해 PHP를 배운다. PHP를 사용하는 근무 환경에서 자신의 작업 속도를 높 이려는 이들도 있을 것이다. 이번 장에서는 PHP로 무엇을 할 수 있는지, PHP의 장점은 무엇 이며 어떻게 작동하는지 알아보고, PHP가 웹사이트 구축이라는 퍼즐에 꼭 들어맞는 언어임을 확인한다. 또한 PHP 언어와의 첫 만남을 통해 작동 과정을 살펴볼 것이다.

1.1 웹 세상에서 PHP의 역할 PHP의 주된 용도는 웹사이트 구축이다. 보통 PHP 프로그램은 개인적 용도로 데스크톱 컴퓨 터에서 실행되기보다는 다수의 사용자가 접근할 수 있는 웹 서버에서 실행된다. 이 경우 사용 자는 자신의 컴퓨터에서 웹 브라우저를 이용해 PHP 프로그램에 접근한다. 이번 절에서는 웹 브라우저와 웹 서버가 상호작용하는 과정에서 PHP가 어떤 역할을 하는지 설명한다. 여러분이 컴퓨터 앞에 앉아 크롬이나 파이어폭스 같은 브라우저로 웹 페이지를 열 때, 여러분 의 컴퓨터는 다른 곳의 컴퓨터와 모종의 대화를 주고받는다. 이 대화가 어떻게 진행되는지, 또 한 이 대화로 인해 어떻게 웹 페이지가 화면에 나타나게 되는지가 [그림 1-1]에 나와 있다.

1장 오리엔테이션과 첫걸음

29


그림 1-1 클라이언트와 서버 간 통신에 PHP가 관여하지 않는 경우

사용자 PC

www.example.com에 요청드립니다. /catalog.html을 보내주세요.

웹 서버 아파치

카드놀이

인터넷 웹 브라우저

워드

페이지 보내드립니다.

아웃룩

디스크 드라이브

그림 안에 번호가 매겨진 각 단계에서는 다음과 같은 일이 벌어진다. ➊ 여러분은 브라우저의 주소표시줄에 www.example.com/catalog.html 을 입력한다. ➋ 브라우저는 www.example.com 이라는 이름을 가진 컴퓨터에 /catalog.html 페이지를 요청한다. 요청 메시지는 인터넷을 통해 전송된다. ➌ 이 메시지는 www.example.com 컴퓨터에서 실행 중인 아파치 HTTP 서버에 전달되고 아파치는 디스크 드라이브에서 catalog.html 파일을 읽어 들인다. ➍ 아파치 HTTP 서버는 브라우저의 요청에 대한 응답으로 위 파일의 내용을 여러분의 컴퓨터로 보낸다. 응답은 인터넷을 통해 전송된다. ➎ 브라우저는 페이지에 담긴 HTML 태그에 맞춰 화면에 페이지를 표시한다.

브라우저가 http ://www .example .com /catalog .html 을 요청할 때마다 웹 서버는

catalog.html 파일의 내용을 똑같이 되돌려준다. 웹 서버의 응답이 바뀌는 유일한 경우는 누 군가 서버에 있는 catalog.html 파일을 수정했을 때다. 하지만 PHP가 끼어들면 서버가 할 일이 늘어난다. 서버는 이 대화의 한쪽 절반을 담당한다. [그림 1-2]는 PHP가 생성한 페이지를 웹 브라우저가 요청했을 때 어떤 일이 벌어지는지 보여 준다.

30

1부 처음 만나는 PHP


그림 1-2 클라이언트와 서버 간 통신에 PHP가 관여하는 경우

사용자 PC 카드놀이

www.example.com에 요청드립니다. /catalog/yak.php를 보내주세요.

웹 브라우저

웹 서버 아파치

인터넷

워드

PHP

페이지 보내드립니다.

아웃룩

MySQL

디스크 드라이브

PHP가 작동하는 대화에서는 번호 순서대로 다음과 같은 일이 벌어진다. ➊ 여러분은 브라우저의 주소표시줄에 www.example.com/catalog/yak.php 를 입력한다. ➋ 브라우저는 www.example.com 이라는 이름을 가진 컴퓨터에 /catalog/yak.php 페이지를 요청한다. 요 청 메시지는 인터넷을 통해 전송된다. ➌ 이 메시지는 www .example .com 컴퓨터에서 실행 중인 아파치 HTTP 서버에 전달된다. www .

example.com 컴퓨터에서는 PHP 엔진이라는 프로그램도 실행되는데, 아파치는 이 PHP 엔진에 다음과 같이 묻는다. “/catalog/yak.php 가 어떻게 생겼어?” ➍P HP 엔진은 디스크 드라이브에서 yak.php 파일을 읽어 들인다. ➎P HP 엔진은 yak.php 파일 내용에 있는 명령을 실행한다. 아마 MySQL 같은 데이터베이스 프로그램과 데 이터를 주고받는 명령일 것이다. ➏P HP 엔진은 “/catalog/yak.php 가 어떻게 생겼어?”라는 아파치 HTTP 서버의 질문에 대한 대답으로

yak.php 프로그램의 출력 결과를 되돌려준다. ➐ 아파치 HTTP 서버는 브라우저의 요청에 응답으로 PHP 엔진에게 받은 페이지 내용을 보낸다. 응답은 인터 넷을 통해 여러분의 컴퓨터로 전송된다. ➑ 브라우저는 페이지에 담긴 HTML 태그에 맞춰 화면에 페이지를 표시한다.

PHP는 프로그래밍 언어이며 PHP 프로그램은 PHP 프로그래밍 언어로 작성된 지시 사항이라 할 수 있다. 그리고 PHP 엔진은 웹 서버에서 PHP 프로그램을 읽고 지시 사항을 수행한다. 프 로그래머들은 종종 프로그래밍 언어 혹은 엔진을 구별하지 않고 ‘PHP’로 통칭하곤 한다. 이 책 에서 ‘PHP’는 프로그래밍 언어를 의미하며, ‘PHP 엔진’은 PHP 프로그램의 명령에 따라 웹 페 이지를 생성하는 존재를 의미한다.

1장 오리엔테이션과 첫걸음

31


PHP (프로그래밍 언어)가 영어(인간의 언어)라면 PHP 엔진은 영어를 구사하는 사람에 비유 할 수 있다. 영어라는 언어는 다양한 단어와 조합을 정의하며 영어 구사자는 이러한 단어의 조 합을 읽거나 듣고 그 의미를 해석하는데, 해석에 따라서는 우유 심부름을 가거나 옷을 입어보 는 등 귀찮은 일을 하기도 한다. PHP 엔진 역시 여러분이 PHP (프로그래밍 언어)로 작성한 프로그램을 해석하고 그에 따라 데이터베이스와 대화하거나 개인화된 웹 페이지를 생성하거나 이미지를 표시한다. 이 책의 관심사는 프로그램 작성에 대한 세부 사항들이다. 예를 들면 [그림 1-2]의 5번째 단계 에서 일어나는 일에 해당한다(자신의 웹 서버에 직접 PHP 엔진을 설치하고 설정하는 방법은 부록 A에 자세히 나와 있다).

PHP가 서버 사이드 언어라 불리는 이유는 [그림 1-2]에 나와 있듯이 웹 서버 쪽에서 작동하 기 때문이다. 반면 클라이언트 사이드 언어로 쓰이는 자바스크립트 같은 언어는 데스크톱 PC 의 웹 브라우저에 내장되어 팝업 창을 띄우는 등의 작업을 담당한다. 일단 웹 서버가 웹 페이지 를 생성해 클라이언트로 전송하고 나면 ([그림 1-2]의 7번째 단계) PHP의 역할은 끝난다. 페 이지에 자바스크립트가 있다면 그 자바스크립트는 클라이언트 쪽에서 실행되며 페이지를 생성 했던 PHP와는 완전히 단절된다. 평범한 HTML 웹 페이지는 내용이 정해진 편지에 비유할 수 있다. 이를테면, 비행기에 탔다가 기내식의 벌레 때문에 놀라 항공사에 문제를 제기했을 때 받게 되는 “음식에서 벌레가 나온 점 에 대해 사과드립니다”와 같은 편지인 셈이다. 고객이 보낸 편지가 항공사의 본사에 도착하면, 과중한 업무에 시달리던 고객지원 부서 직원이 ‘벌레 답신 편지’를 파일함에서 꺼내 복사한 다음 회신 편지로 보낼 것이다. 모든 유사한 요청이 정확히 같은 응답을 받게 된다. 반대로, PHP가 생성하는 동적 페이지는 여러분이 해외 거주 중인 친구에게 쓰는 손편지라고 생각할 수 있다. 원하는 어떤 내용이든 편지에 넣을 수 있다. 잡담, 도표는 물론 시조를 넣을 수 도 있다. 여러분의 귀여운 아기가 사방에 으깬 당근을 뿌려 부엌을 엉망으로 만들어놓았던 일 처럼 훈훈한 이야기도 좋다. 이런 편지의 내용은 편지를 받을 특정인을 위해 재단된다. 하지만 일단 편지를 우체통에 넣고 나면 그 내용을 더는 바꿀 수 없다. 편지는 바다를 건너 날아가 친 구에게 도착한다. 친구가 편지를 읽는 동안 그 편지를 수정할 방법은 없다.

32

1부 처음 만나는 PHP


이제부터 공예에 심취한 친구에게 편지를 쓴다고 상상해보자. 편지 내용에는 “페이지 맨 위에 있는 개구리 그림을 오려서 페이지 하단의 작은 토끼 위에 붙이세요”, “다른 단락을 읽기 전에 마지막 단락을 먼저 읽으세요”와 같은 지시 사항을 덧붙인다. 여러분의 친구는 편지를 읽으면 서 편지에 적힌 지시에 따른다. 이런 지시가 웹 페이지에서는 자바스크립트에 해당한다. 이런 지시는 편지가 쓰일 때 정해지며 그 이후에는 바뀌지 않는다. 그러나 편지의 독자가 지시에 따 르면서 편지 자체가 변화할 수 있다. 비슷한 원리로, 웹 브라우저는 페이지 안의 자바스크립트 에 복종한다. 창을 띄우고, 폼 메뉴의 선택 조건을 변경하고, 새로운 URL로 페이지를 새로고침 한다.

1.2 PHP가 뭐가 그렇게 대단해? 여러분이 PHP에 끌리는 이유는 PHP가 무료이거나 배우기 쉬워서일 수도 있고, 당장 다음 주 부터 PHP 프로젝트를 시작하라는 상사의 지시 때문일 수도 있다. PHP를 사용하기에 앞서 무 엇이 PHP를 특별하게 만들어주는가에 대해 어느 정도 알아두어야 한다. 다음에 누군가가 여 러분에게 “PHP가 뭐가 그렇게 대단해?”라고 물었을 때 이번 절의 내용을 답변 근거로 삼도록 하자.

1.2.1 PHP는 무료다 PHP를 쓸 때는 어딘가에 돈을 낼 필요가 없다. 지하실에 굴러다니는 10년 된 PC에 PHP 엔진 을 설치하든, IDC를 빼곡하게 채운 ‘엔터프라이즈급’ 서버들에 설치하든 마찬가지다. 라이선스 비용, 지원 비용, 유지 비용, 업그레이드 비용을 포함해 그 어떤 종류의 과금도 없다.

macOS 와 리눅스 배포판 대부분에는 PHP가 이미 설치되어 있다. 자신의 macOS나 리눅스 에 PHP가 없거나 윈도우같은 다른 운영체제를 사용한다면 http://www.php.net 에서 자신 의 운영체제에 맞는 PHP를 내려받을 수 있다. PHP 설치 방법은 부록 A에 자세히 나와 있다.

1장 오리엔테이션과 첫걸음

33


1.2.2 PHP는 자유롭다 PHP는 오픈소스 프로젝트로서 누구나 그 내부를 들여다볼 수 있다. PHP가 원하는 대로 작동 하지 않거나 혹은 그저 작동 과정이 궁금하다면 (C 프로그래밍 언어로 작성된) PHP 엔진의 내 부를 뒤져 볼 수 있다. 설령 여러분에게 그 정도로 충분한 기술적 전문 지식이 없다 해도 여러분 을 대신해 줄 누군가가 있을 것이다. 대다수 사람은 자신의 자동차를 직접 고치지 못하지만 수리 기술이 있는 정비공에게 차를 맡기는 것 정도는 할 수 있는 법이다.

1.2.3 PHP는 크로스 플랫폼을 지원한다 PHP는 윈도우, macOS, 리눅스, 다양한 버전의 유닉스 등이 설치된 웹 서버 컴퓨터에서 사용 할 수 있다. 웹 서버 운영체제를 변경하더라도 일반적으로 PHP 프로그램은 전혀 수정할 필요 가 없다. 윈도우 서버에서 유닉스 서버로 그냥 복사하기만 해도 잘 작동한다.

PHP와 함께 사용되는 웹 서버로는 아파치가 손에 꼽힌다. 그 밖에 엔진엑스나 마이크로소프 트 인터넷 정보 서비스(IIS )도 사용할 수 있으며 CGI 표준을 지원하는 웹 서버라면 모두 사용 가능하다. 또한 PHP는 MySQL, PostgreSQL, 오라클, MS SQL 서버, SQLite, 레디스, 몽고

DB 같은 수많은 데이터베이스와 연동할 수 있다. 앞 단락에 나오는 약어들 때문에 겁먹을 필요는 없다. 핵심은, 여러분이 사용하는 시스템이 무 엇이든 PHP는 그 안에서 잘 작동할 것이며 어떤 데이터베이스를 사용하더라도 잘 연동될 것 이라는 점이다.

1.2.4 PHP는 광범위하게 사용된다 셀 수 없이 많은 소규모 개인 홈페이지부터 페이스북, 위키피디아, 텀블러, 슬랙, 야후 같은 대 형 서비스에 이르기까지 각기 다른 200만 개 이상의 웹사이트에서 PHP를 사용한다. 다양한 관련 도서, 잡지, 웹사이트를 통해 PHP를 배울 수 있고 PHP로 무엇을 할 수 있는지 탐색할 수 있다. PHP에 대한 지원과 교육을 제공하는 업체들이 있다. 간단히 말해, 여러분이 PHP 사용 자라면 여러분은 혼자가 아니다.

34

1부 처음 만나는 PHP


1.2.5 PHP는 복잡성을 해소한다 PHP를 쓰면 수백만 고객이 이용할 수 있는 강력한 전자상거래 엔진을 구축할 수 있다. 게시글 이나 언론 기사의 링크 목록을 자동으로 관리하는 소규모 사이트를 구축할 수도 있다. 간단한 프로젝트에 PHP를 사용할 때 좋은 점은, 대규모 시스템에서나 고려할 법한 문제들로 고민할 필요가 없다는 것이다. 캐싱, 자체 라이브러리, 동적 이미지 생성 같은 고급 기능이 필요하다면 얼마든지 쓸 수 있지만, 필요하지 않다면 신경 쓰지 않아도 된다. 그저 기본적인 사용자 입력과 화면 출력 처리에만 초점을 맞출 수 있다.

1.2.6 PHP는 웹 프로그래밍을 위해 만들어졌다 다른 많은 언어와 달리 PHP는 근본적으로 웹 페이지를 생성하기 위한 용도로 탄생했다. PHP 를 사용하면 전송값 접근이나 데이터베이스 통신같이 웹 프로그래밍에서 공통으로 수행하는 작업을 더 쉽게 처리할 수 있다는 의미다. PHP는 HTML 구문을 다루고 날짜와 시간을 조작하 고 웹 쿠키를 관리하는 능력이 있다. 이런 기능들은 일반적으로 다른 언어에서는 추가 라이브 러리를 통해서만 사용할 수 있다.

1.3 PHP 따라 하기 이제 PHP를 맛볼 준비가 되었는가? 이번 절에서는 몇몇 프로그램을 보여주고 각 프로그램이 하는 일을 설명한다. 이 과정에서 모든 내용을 이해하지 못하더라도 걱정할 필요는 없다. 이 책 의 나머지 부분이 해결해줄 것이다. PHP 프로그램이 어떻게 생겼으며 어떻게 작동하는지 윤 곽을 가늠하는 정도로만 따라가면 된다. 세부적인 것에 연연하지 말자.

PHP 엔진은 프로그램을 작동할 때 오로지 PHP 시작 태그와 종료 태그 안에 있는 프로그램 부 분에만 주목하며 태그 밖에 있는 내용은 무엇이든 그대로 출력한다. 이 덕분에 내용이 대부분

HTML로 이루어진 페이지 안에 PHP를 끼워 넣기 쉽다. PHP 엔진은 PHP 시작 태그인 <?php 와, PHP 종료 태그인 ?> 사이에 있는 명령들을 실행한다. PHP 페이지는 일반적으로 이름이

.php 로 끝나는 파일로 작성한다. [예제 1-1]은 PHP 명령이 하나 들어 있는 페이지다.

1장 오리엔테이션과 첫걸음

35


예제 1-1 Hello, World! <html> <head> <title> PHP 첫인사</title> </head> <body> <b> <?php

print "Hello, World!"; ?> </b> </body> </html>

[예제 1-1]은 다음과 같이 출력된다.

<html> <head> <title> PHP 첫인사</title> </head> <body> <b> Hello, World!</b> </body> </html>

웹 브라우저에서는 [그림 1-3]처럼 보인다. 그림 1-3 PHP로 첫인사하기

하지만 고정된 메시지 출력에 PHP를 사용하는 것은 그리 흥미롭지 않다. 평범한 HTML 페이 지에 “Hello, World!” 메시지를 넣어도 같은 결과를 얻을 수 있다. PHP는 동적 데이터, 즉 변

36

1부 처음 만나는 PHP


화하는 정보를 출력할 때 더 유용하다. PHP 프로그램에서 가장 일반적인 정보의 출처 중 하나 는 사용자다. 브라우저는 폼을 보여주고 사용자는 그곳에 정보를 입력한 다음 ‘제출 submit ’ 버튼 을 누른다. 브라우저는 이 정보를 서버로 전송하고 서버는 최종적으로 사용자의 프로그램을 작 동시킬 PHP 엔진으로 정보를 넘겨준다. [예제 1-2]는 PHP 없이 작성된 HTML 폼이다. 이 단순한 폼에는 사용자 이름과 제출 버튼만 있으며 <form> 태그의 action 속성에 지정된 sayhello.php 로 정보를 제출한다. 예제 1-2 데이터를 제출하는 HTML 폼 <form method = "POST" action = "sayhello.php"> 이름: <input type = "text" name = "user" /> <br/> <button type = "submit"> 인사</button> </form>

웹 브라우저는 [예제 1-2]의 HTML을 [그림 1-4]에 있는 폼처럼 표시한다. 그림 1-4 폼 출력

[예제 1-3]은 폼의 텍스트 상자에 입력된 이름을 받아 인사를 출력하는 sayhello.php 프로그 램이다. 예제 1-3 동적 데이터 <?php

print "Hello, ";

1장 오리엔테이션과 첫걸음

37


// 'user' 라는 폼 매개변수로 제출된 값 출력

print $_ POST['user']; print "!"; ?>

텍스트 상자에 Ellen이라고 입력하고 폼을 제출했다면 [예제 1-3]은 Hello, Ellen!을 출력 한다. [그림 1-5]는 웹 브라우저가 이를 어떻게 표시하는지 보여준다. 그림 1-5 폼 매개변수 출력

$_POST에는 폼 매개변수로 제출된 값이 담긴다. 프로그래밍 전문 용어로 변수 variable라 하는데,

담고 있는 값을 바꿀 수 있기 때문에 그렇게 불린다. 특히 둘 이상의 값을 담을 경우에는 배열 변수 array variable라 한다. 배열 변수에 대해서는 따로 7장에서 논의하며 일반적인 변수는 4장에서 논의한다. 이 예제에서 //로 시작하는 행은 주석 행comment line이다. 주석은 소스 코드를 읽게 될 사람을 위 해 존재하며 PHP 엔진은 이를 무시한다. 주석은 프로그램의 정보와 작동 방법을 설명하는 데 유용하다. 주석은 1.4절에서 자세히 설명한다. user 값을 제출하는 HTML 폼 자체를 PHP를 사용해 출력할 수도 있다. [예제 1-4]를 살펴보자. 예제 1-4 폼 출력 <?php

print <<<_ HTML_ <form method = "post" action = "$_ SERVER[PHP_ SELF]"> 이름: <input type = "text" name = "user" /> <br/>

38

1부 처음 만나는 PHP


<button type = "submit"> 인사</button> </form> _ HTML_ ; ?>

[예제 1-4]는 here 문서here-document라 불리는 문자열 구문을 사용한다. <<<_HTML_과 _HTML_ 사이에 있는 모든 내용을 print 명령에 전달해 표시한다. [예제 1-3]과 마찬가지로 문자열 내부에 있는 변수는 해당하는 값으로 교체한다. 이번에 나온 변수는 $_SERVER[PHP_SELF]인 데, 이 변수는 현재 페이지의 URL1을 담고 있는 특별한 변수다. [예제 1-4]의 페이지 URL이

http://www.example.com/users/enter.php 라면 $_SERVER[PHP_SELF]는 /users/enter. php 를 담는다. 폼 action에 $_SERVER[PHP_SELF]를 지정하면 폼을 출력하는 페이지와 폼 제출 데이터를 처 리하는 코드를 한 페이지에 넣을 수 있다. [예제 1-5]는 [예제 1-3]과 [예제 1-4]를 한 페이지 에 조합한 예제다. 폼이 제출되면 인사를 출력하고 그렇지 않을 때는 폼을 출력한다. 예제 1-5 환영인사와 폼 출력 <?php // 폼이 전송되면 인사하기

if ($_ POST['user']) { print "Hello, "; // 'user'라는 폼 매개변수로 제출된 값 출력 print $_ POST['user']; print "!"; } else { // 그렇지 않다면 폼 출력

print <<<_ HTML_ <form method = "post" action = "$_ SERVER[PHP_ SELF]"> 이름: <input type = "text" name = "user" /> <br/> <button type = "submit"> 인사</button> </form> _ HTML_ ; } ?>

1 프로토콜(예: http://)이나 호스트명(예: www.example.com)을 제외한 URL

1장 오리엔테이션과 첫걸음

39


이름: <input type = "text" name = "my_ name" > <br> <input type = "submit" value = "인사"> </form> _ HTML_ ; }

1장에 나왔던 클라이언트와 서버의 통신에 대한 그림을 기억하는가? [그림 7-1]은 [예제 7-1] 의 폼을 표시하고 처리하는 과정에서 오가는 클라이언트와 서버의 통신을 나타낸다. 첫 번째 요청-응답 과정은 브라우저에 폼을 출력한다. 두 번째 요청-응답 과정은 폼으로 제출된 데이 터를 서버에서 처리하고 브라우저에 결과를 출력한다. 그림 7-1 간단한 폼 출력과 처리

1단계: 화면에 폼 출력하기 웹 브라우저

웹 서버 "Get /form.php"

폼 변수 my_name이 제출되지 않았네? 그렇다면 폼 출력 HTML을 보내야겠군.

웹 브라우저

이름: 인사

<form method="post" action="/form.php"> 이름: <input type="text" name="my_name"/> <br> <input type="submit" value="인사"/> </form>

2단계: 폼을 제출하고 결과를 출력하기 웹 서버

웹 브라우저

이름:

"POST /form.php" my_name=선아

인사

인사를 해야 해.

웹 브라우저

선아님 안녕하세요 선아님 안녕하세요

174 3부 동적 웹사이트 구축 실전

폼 변수 my_name에 값이 있군.


첫 번째 요청에 대한 응답은 간단한 form을 표시하는 HTML이다. [그림 7-2]는 이 응답을 브 라우저가 출력한 화면이다. 그림 7-2 간단한 폼

두 번째 요청에 대한 응답은 제출된 폼 데이터를 처리한 결과다. [그림 7-3]은 텍스트박스에 선아라고 입력하고 폼을 제출했을 때 출력되는 화면이다. 그림 7-3 폼 제출 이후

[예제 7-1]의 실행 과정을 간단히 표현하면 ‘폼 데이터가 제출되면 처리하고, 그렇지 않으면 폼 을 출력하라’인데, 간단한 프로그램에 보편적으로 쓰이는 형식이다. 기본적인 폼을 설계할 때, 폼 을 출력하는 코드와 폼을 처리하는 코드를 한 페이지에 두면 폼과 로직을 동시에 관리하기 편하 다. 이후에 더 복잡한 폼을 다루게 되면 출력 코드와 처리 코드를 각기 다른 파일로 나눌 것이다. 앞의 예제에서 폼을 제출하는 URL은 처음 폼을 요청했던 URL과 동일하다. <form> 태그의 action 속성에 설정된 값이 $_SERVER['PHP_SELF']라는 특수한 변수이기 때문이다. $_SERVER

변수는 자동 전역변수며 서버와 현재 요청에 대한 다양한 정보 및 PHP 엔진의 실행 정보 등을 저장한다. $_SERVER의 PHP_SELF 원소에는 현재 요청 URL의 일부 경로명이 저장된다. 예를 들

7장 사용자와 정보 주고받기: 웹 폼 제작 175


어 http://www.example.com/store/catalog.php 라는 URL을 통해 PHP 스크립트에 접 근했다면 $_SERVER['PHP_SELF']의 값은 /store/catalog.php다.1 $_SERVER['REQUEST_METHOD']도 간단한 폼에서 사용하기 좋다. 이 배열 원소는 브라우저가

어떤 HTTP 메서드로 현재 페이지를 요청했는지를 나타낸다. 일반적인 웹 페이지에서 이 값은 항상 GET 또는 POST다. 통상적으로 GET은 평범한 웹 페이지를 가져올 때, POST는 폼을 제출할 때 사용한다. $_SERVER['REQUEST_METHOD']의 값은 <form> 태그의 action 속성 값에 관계없 이 항상 대문자다. 따라서 $_SERVER['REQUEST_METHOD']가 POST인지 확인하면 폼이 제출됐는 지 혹은 일반적인 페이지 요청인지 구별할 수 있다. $_POST 배열은 제출된 폼 데이터가 저장된 자동 전역변수다. $_POST 배열의 각 키는 폼 요소의

이름이며 키에 연결된 값은 해당 폼 요소의 값이다. [예제 7-1]의 텍스트 박스의 name 속성은 my_name이므로, 여기에 어떤 값을 입력하고 제출 버튼을 클릭하면 $_POST['my_name']에 모두

저장된다. [예제 7-1]의 구조는 이번 장에서 배울 폼 처리 과정의 뼈대다. 하지만 중대한 결함이 있는데, print $_POST['my_name']. "님 안녕하세요"; 구문이 폼 매개변수 my_name에 입력된 값을 그

대로 출력한다. 외부에서 입력된 값을 이렇게 아무런 조치 없이 출력하면 위험하다. 폼 매개 변수처럼 프로그램 외부에서 유입된 데이터에는 HTML이나 자바스크립트가 포함될 수 있다.

7.4절 ‘데이터 검증’에서는 외부 입력의 위험성을 제거해 프로그램을 안전하게 보호하는 방법 을 설명한다. 이번 장의 나머지 부분은 폼 처리 기법을 여러 가지 측면에서 자세히 설명한다. 7.2절 ‘폼 매개 변수 접근’에서는 동시에 여러 값을 제출하는 폼 매개변수 등 다양한 형태의 폼 입력을 처리하 는 특수한 기법들을 알아본다. 7.3절 ‘폼 처리 함수’는 폼을 유연하게 처리하고 더 단순하게 관 리할 수 있는 함수 기반 구조를 다룬다. 또한 이 구조를 이용하면 제출된 폼 데이터에 예상치 못한 값이 있는지 검사할 수 있다. 7.4절 ‘데이터 검증’에서는 폼 데이터를 검증하는 여러 가지 방법을 설명한다. 7.5절 ‘기본값 표시’에는 폼 요소에 기본값을 제공하고, 폼 화면을 다시 열어 도 사용자가 입력했던 값을 유지하는 방법에 대한 예시가 있다. 마지막으로 7.6절 ‘전부 합치 기’에서는 함수 기반 구조, 유효성 검증, 오류 메시지 표시, 기본값, 사용자 입력값 유지, 폼 데 이터 처리 등 이번 장의 내용을 총망라해 완전한 하나의 폼을 만든다. 1 [예제 4-19]처럼 $_SERVER['PHP_SELF']도 원소명에 따옴표를 빼고 here 문서에 넣으면 올바르게 값을 삽입할 수 있다.

176 3부 동적 웹사이트 구축 실전


7.1 유용한 서버 변수 자동 전역변수인 $_SERVER에는 PHP_SELF, REQUEST_METHOD처럼 웹 서버와 현재 요청에 대한 정보를 제공하는 유용한 원소가 많다. [표 7-1]은 $_SERVER 변수의 원소들이다. 표 7-1 $_SERVER 항목 원소

QUERY_STRING

예시

category= kitchen&price=5

설명 전체 URL에서 물음표 뒤의 매개변수 부분. 왼쪽의 예시는 다음 URL의 쿼리 문자열이다.

http://www.example.com/catalog/store.php?category =kitchen&price=5 URL에 붙이는 추가 경로 정보. 마지막 슬래시부터 URL 끝까지 이어지는 문자열이다. 쿼리 스트링을 사용하지 않고 스크립트에 정

PATH_INFO

/browse

보를 전달하는 수단으로 쓰인다. 왼쪽 예시는 다음 URL의 PATH_

INFO다.

http://www.example.com/catalog/store.php/browse SERVER_NAME

www.example.com

해당 PHP 엔진을 실행하는 웹사이트의 이름. 웹 서버에서 다수의 가 상 도메인이 운영 중이라면 현재 접속한 가상 도메인명이 지정된다. 웹사이트 문서가 위치한 웹 서버 컴퓨터의 디렉터리. http://www.

DOCUMENT_ ROOT

/usr/local/htdocs

REMOTE_ADDR

175.56.28.3

REMOTE_HOST

pool0560.cvx. dialup.verizon.net

2 HTTP_REFERER

HTTP_USER_ AGENT

http://shop. oreilly.com/ product/0636920 029335.do

example .com 웹사이트의 문서 최상위 경로가 /usr /local / htdocs 라면, http ://www .example .com /catalog /store . php 에 해당하는 파일은 /usr /local /htdocs /catalog /store . php 다. 웹 서버에 요청을 보내는 사용자의 IP 주소다. 웹 서버에 요청을 보낸 사용자의 IP 주소를 호스트명으로 전환한 값. 웹 서버가 이 전환 작업을 수행할 때 드는 비용이 (소요 시간 측면에 서) 비교적 높으므로, 대부분의 웹 서버는 이 작업을 하지 않는다. 누군가 링크를 클릭해 현재 URL에 들어왔을 때, 링크가 있던 페이 지의 URL이 HTTP_REFERER에 저장된다. 이 값은 변조할 수 있으므로 중요한 웹 페이지의 접근 권한을 이 값만으로 판단하면 안 된다. 하지만 링크가 연결된 현황을 파악하는 용도로 요긴하게 사용 할 수 있다.

Mozilla/5.0 (Macintosh; Intel 페이지를 요청하는 웹 브라우저의 정보. 예시는 macOS에서 파이 Mac macOS 10.10; rv: 어폭스 37을 실행했을 때의 값이다. HTTP_REFERER처럼 이 37.0) Gecko/20100101 값도 변조의 위험이 있지만, 분석 자료로 활용할 수 있다. Firefox/37.0

2 올바른 철자는 HTTP_REFERRER지만 인터넷 초기 규약 문서에 R이 하나 빠진 채로 잘못 기재된 이래 웹 프로그래밍에서 종종 이렇 게 쓰인다.

7장 사용자와 정보 주고받기: 웹 폼 제작 177


7.2 폼 매개변수 접근 페이지 요청을 받으면 PHP 엔진은 먼저 URL이나 폼으로 제출된 모든 매개변수의 값을 담은 자동 전역변수를 생성한다. GET 메서드 폼의 매개변수와 URL 매개변수는 $_GET에, POST 메서 드 폼의 매개변수는 $_POST에 들어간다. 예를 들어 http ://www .example .com /catalog .php ?product _ id = 21 &category =

fryingpan 이라는 URL은 두 개의 값을 $_GET에 넣는다. $_GET['product_id']에는 21이 들어간다.

●●

$_GET['category']에는 fryingpan이 들어간다.

●●

다음에 살펴볼 [예제 7-2]의 폼에서 텍스트 박스에 21을 입력하고 메뉴 중에서 프라이팬을 고 르면 앞서 GET 예시와 같은 값이 $_POST에 들어간다. 예제 7-2 요소가 두 개인 폼 <form method = "POST" action = "catalog.php"> <input type = "text" name = "product_ id"> <select name = "category"> <option value = "ovenmitt"> 냄비받침</option> <option value = "fryingpan"> 프라이팬</option> <option value = "torch"> 주방토치</option> </select> <input type = "submit" name = "제출"> </form>

다음으로 살펴볼 [예제 7-3]은 [예제 7-2]의 폼을 이용해 만든 전체 PHP 프로그램이며, 폼을 표시한 뒤 $_POST의 값을 출력한다. [예제 7-3]은 <form> 태그의 action 속성에 catalog.php 를 지정했기 때문에 catalog.php 라는 파일명으로 웹 서버에 저장한다. 다른 파일명으로 저장 하면 action 속성도 그에 따라 바꿔주어야 한다. 예제 7-3 제출된 폼 매개변수 출력 <form method = "POST" action = "catalog.php"> <input type = "text" name = "product_ id">

178 3부 동적 웹사이트 구축 실전


<select name = "category"> <option value = "ovenmitt"> 냄비받침</option> <option value = "fryingpan"> 프라이팬</option> <option value = "torch"> 주방토치</option> </select> <input type = "submit" name = "제출"> </form> 제출된 값은 다음과 같습니다:

product_ id: <?php print $_ POST['product_ id'] ?? '' ?> <br/> category: <?php print $_POST['category'] ?? '' ?>

제출된 POST 변수가 없을 때 PHP가 경고 메시지를 출력하지 않도록 [예제 7-3]에서는 ??를 사용한다. ??는 널 병합 null coalesce 연산자다. $_POST['product_id'] ?? '' 구문은 $_POST['product_id']에 값이 있으면 해당값을 그대로

나타내고, 그렇지 않으면 빈 문자열을 나타낸다. 이렇게 하지 않으면, GET 메서드로 페이지를 호출하고 POST 변수가 없을 때 PHP Notice: Undefined index: product_id라는 메시지가 나타날 수도 있다.

CAUTION _ 널 병합 연산자는 PHP 7에서 도입됐다. 이전 버전을 쓰고 있다면 다음과 같이 isset() 함 수를 사용하라. if (isset($_ POST['product_ id'])) { print $_ POST['product_ id']; }

폼 요소의 이름 끝에 []를 붙이면 한 요소로 복수의 값을 전달할 수 있다. 한 요소로 여러 값이 전달되면 PHP 엔진은 각각의 값을 배열의 원소로 취급한다. [예제 7-4]의 <select> 메뉴에서 선택한 값들은 $_POST['lunch']로 제출된다. 예제 7-4 다중 값을 지닌 폼 요소 <form method = "POST" action = "eat.php"> <select name = "lunch[]" multiple>

7장 사용자와 정보 주고받기: 웹 폼 제작 179


<option value = "바베큐 돼지고기"> 바베큐 돼지고기 번</option> <option value = "닭고기"> 닭고기 번</option> <option value = "연꽃씨"> 연꽃씨 번</option> <option value = "단팥"> 단팥 번</option> <option value = "제비집"> 제비집 번</option> </select> <input type = "submit" name = "제출"> </form>

[예제 7 -4 ]의 폼에서 닭고기 번과 제비집 번을 선택해 제출하면 $_POST['lunch']에는 chicken과 nest를 원소로 지닌 배열이 저장된다. 일반적인 다차원 배열 문법으로 이 값들에

접근할 수 있다. [예제 7-5]는 [예제 7-4]의 폼을 이용해 만든 전체 PHP 프로그램이며 선택된 메뉴들을 출력한다. 파일명과 action 속성의 관계에 대한 조건은 앞서와 같으므로 [예제 7-5] 의 코드는 eat.php 라는 파일에 저장해야 하며, 그렇지 않을 때는 <form> 태그의 action 속성 값과 파일명을 일치시켜야 한다. 예제 7-5 다중 값으로 제출된 변수에 접근하기 <form method = "POST" action = "eat.php"> <select name = "lunch[]" multiple> <option value = "바베큐 돼지고기"> 바베큐 돼지고기 번</option> <option value = "닭고기"> 닭고기 번</option> <option value = "연꽃씨"> 연꽃씨 번</option> <option value = "단팥"> 단팥 번</option> <option value = "제비집"> 제비집 번</option> </select> <input type = "submit" name = "제출"> </form> 원하시는 번을 선택하세요: <br/> <?php

if (isset($_ POST['lunch'])) { foreach ($_ POST['lunch'] as $choice) { print "$choice 번을 골랐습니다. <br/> "; } } ?>

180 3부 동적 웹사이트 구축 실전


[예제 7-5]의 메뉴에서 닭고기 번과 제비집 번을 선택하면 폼 제출 후의 출력 결과는 다음과 같다.

원하시는 번을 선택하세요: 닭고기 번을 골랐습니다. 제비집 번을 골랐습니다.

lunch[] 요소로 닭고기 번과 제비집 번을 선택하고 폼을 제출하면 다음과 같은 PHP 코드로

변환된다고 생각해도 좋다.

$_POST['lunch'][] = '닭고기'; $_POST['lunch'][] = '제비집';

[예제 4-6]과 마찬가지로 이 문법은 배열 마지막에 원소를 추가한다.

7.3 폼 처리 함수 [예제 7-1]처럼 기본적인 폼도 출력 코드와 처리 코드를 분리해 개별 함수로 만들면 한층 유연 한 구조로 발전시킬 수 있다. [예제 7-6]은 [예제 7-1]에 함수를 도입한 코드다. 예제 7-6 함수를 도입한 환영 인사 폼 // 요청 메서드에 따라 실행할 함수를 결정하는 로직

if ($_ SERVER['REQUEST_ METHOD'] = = 'POST') { process_ form(); } else { show_ form(); } // 폼을 제출하면 수행하는 함수

function process_ form() { print $_ POST['my_ name']. "님 안녕하세요."; }

7장 사용자와 정보 주고받기: 웹 폼 제작 181


// 폼을 표시하는 함수

function show_ form() { print<<<_ HTML_ <form method = "POST" action = "$_ SERVER[PHP_ SELF]"> 이름: <input type = "text" name = "my_ name" > <br/> <input type = "submit" value = "인사"> </form> _ HTML_ ; }

이제 폼을 변경하려면 show_form() 함수를, 폼이 제출된 후 수행되는 작업을 변경하려면 process_form() 함수의 내용을 수정하면 된다.

폼 처리와 출력을 각각 함수로 분리하면 데이터 검증 단계를 추가하기 쉽다. 7.4절에서 다룰 데이터 검증은 폼 입력을 처리하는 모든 웹 애플리케이션의 핵심적인 기능이다. 폼이 제출되면 먼저 데이터의 유효성을 검사하고 나서 폼을 처리해야 한다. [예제 7-7]은 [예제 7-6]에 검증 함수를 추가한다. 예제 7-7 폼 데이터 검증 // 요청 메서드에 따라 실행할 함수를 결정하는 로직

if ($_ SERVER['REQUEST_ METHOD'] = = 'POST') { if (validate_ form()) { process_ form(); } else { show_ form(); } } else {

show_ form(); } // 폼을 제출하면 수행하는 함수

function process_ form() { print $_ POST['my_ name']. "님 안녕하세요."; } // 폼을 표시하는 함수

182 3부 동적 웹사이트 구축 실전


function show_ form() { print<<<_ HTML_ <form method = "POST" action = "$_ SERVER[PHP_ SELF]"> 이름: <input type = "text" name = "my_ name" > <br/> <input type = "submit" value = "인사"> </form> _ HTML_ ; } // 폼 데이터 검사

function validate_ form() { // my_ name의 글자수가 3보다 작은가? if (strlen($_ POST['my_ name']) < 3) { return false; } else { return true; } }

[예제 7-7]의 validate_form() 함수는 $_POST['my_name']가 세 글자보다 짧으면 false를 반환하고 그렇지 않으면 true를 반환한다. validate_form()은 폼이 제출됐을 때 페이지 맨 위에서 호출된다. validate_form() 이 true를 반환하면 process_form() 이 호출되며 그렇 지 않으면 show_form()이 호출된다. 즉 Bob이나 Bartholomew처럼 세 글자 이상의 이름을 입 력하고 폼을 제출하면 앞선 예제와 마찬가지로 Bob님 안녕하세요. 또는 Bartholomew님 안녕 하세요.가 출력된다는 뜻이다. 그러나 BJ라고 입력하거나 아예 입력하지 않으면 validate_ form()이 false를 반환하고 process_form()은 절대 실행되지 않는다. 대신 show_form()을

호출하고 폼을 다시 출력한다. [예제 7-7]은 폼에 입력한 이름을 validate_form()으로 검사하지만, 실패했을 때는 그 이유 를 알려주지 않는다. 사용자가 제출한 데이터가 유효성 검사를 통과하지 못하면 폼을 다시 보 여주는데, 모범적인 폼이라면 이때 오류를 설명하는 한편 올바르게 입력했던 값들을 해당 폼 요소에 그대로 다시 넣어준다. 다음 절에서는 오류 메시지를 표시하는 방법을 보여주고, 7.5절 ‘기본값 표시’에서는 사용자가 입력한 값을 안전하게 다시 표시하는 방법을 설명한다.

7장 사용자와 정보 주고받기: 웹 폼 제작 183


[예제 8-8]에서 exec()는 쿼리를 성공적으로 실행해도 반영된 로우가 없으면 0을 반환하므로, 실제 오류의 반환값(false )과 구별하기 위해 삼중 등호로 비교한다. errorInfo()는 오류 정 보를 나타내는 3가지 원소가 담긴 배열을 반환한다. 첫 번째 원소는 SQLSTATE 오류 코드다. 이 코드는 다양한 데이터베이스 프로그램 사이에서 거의 표준화된 코드로, 예제의 HY000은 모 든 일반 오류를 나타낸다. 두 번째 원소는 데이터베이스 프로그램에 따라 다른 전용 오류 코드 를 나타내며, 세 번째 원소는 오류를 설명하는 텍스트 메시지다. [예제 8-9]처럼 PDO::ATTR_ERRMODE 속성에 PDO::ERRMODE_WARNING을 지정하면 경고 모드로 설정된다. 이 모드는 침묵 모드와 마찬가지로 예외를 발생시키지 않으며 오류가 발생했을 때 false를 반환하지만, PHP 엔진이 경고 수준의 오류 메시지를 생성한다. 이 메시지는 오류 처

리 설정에 따라 화면 또는 로그 파일로 출력된다. 추후 12장에서 오류 메시지가 출력될 위치를 제어하는 방법을 알려준다. 예제 8-9 경고 모드 // 생성자는 실행 실패 시 항상 예외를 발생시킨다.

try { $db = new PDO('sqlite:/tmp/restaurant.db'); } catch (PDOException $e) { print "접속할 수 없습니다: " . $e- > getMessage(); } $db- > setAttribute(PDO::ATTR_ ERRMODE, PDO::ERRMODE_ WARNING); $result = $db- > exec("INSERT INTO dishes (dish_ size, dish_ name, price, is_ spicy)

VALUES ('대', '참깨 퍼프', 2.50, 0)"); if (false = = = $result) { $error = $db- > errorInfo(); print "데이터를 추가할 수 없습니다!\n"; print "SQL Error = {$error[0]}, DB Error = {$error[1]}, Message = {$error[2]}\n"; }

[예제 8-9]는 [예제 8-8]과 같은 출력 결과를 생성하지만 다음과 같은 오류 메시지도 함께 출 력한다.

PHP Warning: PDO::exec(): SQLSTATE[HY000]: General error: 1 table dishes has no column named dish_size in error- warning.php on line 10

226 3부 동적 웹사이트 구축 실전


SQL 수업 : INSERT INSERT 명령은 데이터베이스 테이블에 로우를 추가한다. [예제 8-10]은 INSERT의 문법이다. 예제 8-10 데이터 추가

INSERT INTO 테이블 (컬럼1[, 컬럼2, 컬럼3, ...]) VALUES (값1[, 값2, 값3, ...])

[예제 8-11]의 INSERT 쿼리는 dishes 테이블에 새로운 메뉴를 추가한다. 예제 8-11 신규 메뉴 추가

INSERT INTO dishes (dish_ id, dish_ name, price, is_ spicy) VALUES (1, '데친 해삼', 6.50, 0)

데친 해삼과 같은 문자열 값은 SQL 쿼리에 사용할 때 작은따옴표로 감싸야 한다. 작은따옴표는

문자열 구분자로 인식되므로, 쿼리에 작은따옴표를 자체를 넣어야 할 때는 작은따옴표를 연이어 두 번 써서 이스케이프해야 한다. [예제 8-12]는 '특별'한 치킨이라는 메뉴를 dishes 테이블 에 추가한다. 예제 8-12 문자열 값 사용

INSERT INTO dishes (dish_ id, dish_ name, price, is_ spicy) VALUES (2, '''특별''한 치킨', 6.75, 1)

VALUES 앞의 괄호에 나열된 컬럼 개수와 VALUES 뒤의 괄호에 나열된 값의 개수는 반드시 일치 해야 한다. 일부 컬럼만으로 로우를 추가하려면 [예제 8-13]처럼 특정 컬럼들을 나열하고 그에 해당하는 값들을 알맞게 넣는다. 예제 8-13 일부 컬럼만으로 데이터 추가하기

INSERT INTO dishes (dish_ name, is_ spicy) VALUES ('조개구이', 0)

INSERT 명령은 단축 표현을 쓸 수 있는데, 모든 컬럼 값을 나열하는 경우 컬럼 목록을 생략할 수 있다.

8장 정보 저장: 데이터베이스 227


[예제 8-14]는 [예제 8-11]과 같은 기능을 수행한다. 예제 8-14 모든 컬럼 값을 이용해 데이터 추가하기

INSERT INTO dishes VALUES (1, '데친 해삼', 6.50, 0)

데이터를 변경하려면 exec() 함수로 UPDATE를 실행한다. [예제 8-15]는 몇 가지 UPDATE 구문 을 보여준다. 예제 8-15 exec()로 데이터 변경하기

try { $db = new PDO('sqlite:/tmp/restaurant.db'); $db- > setAttribute(PDO::ATTR_ ERRMODE, PDO::ERRMODE_ EXCEPTION); // 칠리소스 가지 볶음을 매운맛 메뉴로 고친다. // 몇 개의 로우가 반영되었는지 알 필요 없으면 // exec()의 반환값이 필요하지 않다. $db- > exec("UPDATE dishes SET is_ spicy = 1

WHERE dish_ name = '칠리소스 가지 볶음'"); // 칠리소스 랍스터를 매운맛 메뉴로 고치고 가격을 두 배로 올린다. $db- > exec("UPDATE dishes SET is_ spicy = 1, price = price * 2

WHERE dish_ name = '칠리소스 랍스터'"); } catch (PDOException $e) {

print "데이터를 삽입할 수 없습니다: " . $e- > getMessage(); }

또한 데이터를 삭제하려면 exec() 함수로 DELETE를 실행한다. [예제 8-16]은 두 가지 DELETE 구문을 보여준다. 예제 8-16 exec()로 데이터 삭제하기

try { $db = new PDO('sqlite:/tmp/restaurant.db'); $db- > setAttribute(PDO::ATTR_ ERRMODE, PDO::ERRMODE_ EXCEPTION); // 비싼 메뉴 삭제

if ($make_ things_ cheaper) {

228 3부 동적 웹사이트 구축 실전


$db- > exec("DELETE FROM dishes WHERE price > 19.95"); } else { // 또는, 모든 메뉴 삭제 $db- > exec("DELETE FROM dishes"); } } catch (PDOException $e) {

print "데이터를 삭제할 수 없습니다: " . $e- > getMessage(); }

SQL 수업 : UPDATE UPDATE 명령은 테이블의 기존 데이터를 변경한다. [예제 8-17]은 UPDATE의 문법이다. 예제 8-17 데이터 수정

UPDATE 테이블명 SET 컬럼1 = 값1[, 컬럼2 = 값2, 컬럼3 = 값3, ...] [WHERE 표현식]

[예제 8-18]처럼 컬럼의 값을 문자열이나 숫자로 변경할 수 있다. [예제 8-18]에서 ;로 시작하 는 줄은 SQL 주석이다. 예제 8-18 컬럼값 변경하기 ; 테이블의 모든 로우의 price를 5.50으로 변경한다.

UPDATE dishes SET price = 5.50 ; 테이블의 모든 로우의 is_ spicy를 1로 변경한다.

UPDATE dishes SET is_ spicy = 1

변경값 대신 표현식을 사용할 수 있으며 컬럼명도 쓸 수 있다. [예제 8-19]의 쿼리는 각 메뉴의 가격을 2배로 변경한다. 예제 8-19 UPDATE 표현식에 컬럼명 사용하기

UPDATE dishes SET price = price * 2

지금까지 본 UPDATE 쿼리는 dishes 테이블의 모든 로우를 변경한다. 일부 로우의 값만 변경하 려면 UPDATE에 WHERE 절을 추가한다.

8장 정보 저장: 데이터베이스 229


WHERE 절은 변경할 로우를 한정하는 논리적 표현식으로, UPDATE 쿼리는 오직 WHERE 절과 일치 하는 로우만 변경한다. [예제 8-20]은 WHERE 절이 있는 2가지 UPDATE 쿼리를 보여준다. 예제 8-20 UPDATE에 WHERE 절 사용하기 ; 칠리소스 가지 볶음의 맵기 변경

UPDATE dishes SET is_ spicy = 1 WHERE dish_ name = '칠리소스 가지 볶음' ; '특별'한 치킨의 가격 인하

UPDATE dishes SET price = price - 1 WHERE dish_ name = '''특별''한 치킨'

WHERE 절에 대해서는 뒤에서 설명할 컬럼 ‘SQL 수업 : SELECT’에서 더 자세히 설명한다.

exec()는 UPDATE나 DELETE 구문에 의해 변경되거나 삭제된 로우의 수를 반환한다. 이 반환값

을 사용하면 해당 쿼리에 의해 얼마나 많은 로우가 반영되었는지 알아낼 수 있다. [예제 8-21] 은 UPDATE 쿼리로 몇 개의 로우가 가격이 변경되었는지 보고한다. 예제 8-21 UPDATE나 DELETE가 몇 개의 로우에 반영되었는지 알아내기 // 일부 요리의 가격 변경 $count = $db- > exec("UPDATE dishes SET price = price + 5 WHERE price > 3");

print '총 ' . $count . '개 메뉴의 가격이 변경되었습니다.';

가격이 3보다 큰 메뉴가 2개일 때 [예제 8-21]의 출력 결과는 다음과 같다.

총 2개 메뉴의 가격이 변경되었습니다.

230 3부 동적 웹사이트 구축 실전


SQL 수업 : DELETE DELETE 명령은 테이블에서 로우를 삭제한다. [예제 8-22]는 DELETE의 문법이다. 예제 8-22 테이블에서 로우 삭제하기

DELETE FROM 테이블명 [WHERE 표현식]

WHERE 절이 없으면 DELETE는 테이블의 모든 로우를 삭제한다. [예제 8-23]은 dishes 테이블 을 깨끗이 비우는 쿼리다. 예제 8-23 테이블의 모든 로우 삭제하기

DELETE FROM dishes

WHERE 절을 쓰면 DELETE는 WHERE 절과 일치하는 로우만 삭제한다. [예제 8-24]는 WHERE 절 이 포함된 2가지 DELETE 쿼리다. 예제 8-24 테이블에서 일부 로우 삭제하기 ; 가격이 10.00보다 큰 로우를 지운다. DELETE FROM dishes WHERE price > 10.00 ; 메뉴명이 정확히 "호두 번"인 로우를 지운다.

DELETE FROM dishes WHERE dish_ name = '호두 번'

SQL에 UNDELETE 명령은 없으므로 DELETE 명령은 신중하게 사용하자.

8.5 데이터 삽입 보안 이미 4장에서 배웠듯이, 위험 요소가 제거되지 않은 데이터를 그대로 출력하면 크로스 사이트 스크립팅 공격에 무방비상태로 노출된다. 유사하게, SQL 쿼리를 점검하지 않고 그대로 사용하 면 SQL 주입 공격에 노출될 수 있다.

8장 정보 저장: 데이터베이스 231


사용자가 새로운 메뉴를 제안할 수 있는 웹 폼을 가정해 보자. 이 폼에는 new_dish_name이라 는 텍스트 요소가 있고 신규 메뉴의 이름을 입력받는다. [예제 8-25]는 SQL 주입 공격에 취약 한 코드로, exec()를 호출해 dishes 테이블에 새로운 메뉴를 추가한다. 예제 8-25 안전하지 않은 폼 데이터 삽입 $db- > exec("INSERT INTO dishes (dish_ name)

VALUES ('$_POST[new_dish_name]')");

제출된 new_dish_name의 값이 PHP의 큰따옴표 문자열 규칙에 의해 INSERT 구문에 삽입된 다. 만약 유부처럼 평범한 단어라면 전체 쿼리는 INSERT INTO dishes (dish_name) VALUES ('유부')가 되어 정상적으로 실행된다. 그러나 쿼리에 작은따옴표가 들어가면 문제가 생긴다.

제출된 new_dish_name의 값이 '특별'한 치킨이면 전체 쿼리는 INSERT INTO dishes (dish_ name) VALUES (''특별'한 치킨')이 된다. 이 쿼리는 의도치 않게 데이터베이스 프로그램에

혼란을 주는데, 데이터베이스는 시작 괄호 다음의 두 개의 작은따옴표를 빈 문자열로 인식하 고, 이어지는 특별'한 치킨' 부분을 구문 오류라고 판단한다. 더 심각한 문제는, 특수하게 조작된 문자열을 누군가 고의로 입력해 데이터베이스에 피해를 줄 수 있다는 점이다. 다음과 같이 오싹한 입력값을 보라.

x'); DELETE FROM dishes; INSERT INTO dishes (dish_ name) VALUES ('y

이 같은 값이 입력되면 전체 쿼리는 다음과 같이 완성된다.

INSERT INTO DISHES (dish_name) VALUES ('x'); DELETE FROM dishes; INSERT INTO dishes (dish_name) VALUES ('y')

몇몇 데이터베이스는 세미콜론으로 분리된 복수의 쿼리를 exec() 호출 한 번으로 실행할 수 있다. 그런 데이터베이스에 이 쿼리를 전송하면, x라는 메뉴가 삽입되고 모든 메뉴가 삭제된 다음 y 메뉴가 삽입된다. 결과적으로 dishes 테이블이 파괴되는 상황을 초래한다.

232 3부 동적 웹사이트 구축 실전


치밀하게 구성된 입력값을 제출함으로써, 공격자는 자신이 원하는 임의의 SQL 구문을 데이터 베이스 프로그램에 주입할 수 있다. 이런 공격을 방어하려면 SQL 쿼리의 특수 문자를 이스케 이프해야 하며 이때 가장 중요한 요소는 작은따옴표다. 이 작업을 수월하게 할 수 있도록 PDO 는 준비된 구문prepared statements, 다시 말해 Prepared 구문이라 불리는 유용한 기능을 제공 한다.

Prepared 구문 기능은 쿼리 실행을 두 단계로 분리한다. 우선 쿼리문에서 값이 삽입될 부분에 ?를 넣어 자리를 표시한 다음 PDO의 prepare() 메서드로 전달한다. 이 메서드를 호출하면 PDOStatement 객체가 반환된다.

다음으로 쿼리문의 ? 자리를 대체할 값들로 배열을 구성해 PDOStatement의 execute()에 전 달한다. execute() 메서드는 전달받은 값들을 쿼리에 삽입하기 전에 따옴표를 적절하게 처리 하고, SQL 주입 공격으로부터 쿼리를 보호한다. [예제 8-26]은 [예제 8-25]를 안전하게 수행 하는 코드다. 예제 8-26 폼 데이터의 안전한 삽입 $stmt = $db- > prepare('INSERT INTO dishes (dish_ name) VALUES (?)'); $stmt- > execute(array($_POST['new_dish_name']));

쿼리 내부의 각 자리표시는 따옴표로 감싸지 않아도 PDO가 알아서 처리해준다. 쿼리에 여러 값을 삽입하려면 자리마다 표시자를 넣고 해당 값들을 배열로 구성한다. [예제 8-27]은 3개의 자리표시자가 있는 쿼리를 실행한다. 예제 8-27 복수의 자리표시자 사용하기 $stmt = $db- > prepare('INSERT INTO dishes (dish_ name,price,is_ spicy) VALUES (?,?,?)'); $stmt- > execute(array($_ POST['new_ dish_ name'], $_ POST['new_ price'], $_POST['is_spicy']));

8장 정보 저장: 데이터베이스 233


8.6 데이터 입력 폼 프로그램 지금부터 살펴볼 [예제 8-28]은 이번 장에서 다루었던 데이터베이스 사용법과 7장의 폼 처리 코드를 조합한 프로그램이다. 폼을 출력하고, 제출된 값을 검사하고, 데이터베이스 테이블에 데이터를 저장한다. 폼은 메뉴의 이름, 가격, 맵기를 입력받고, 제출한 정보는 dishes 테이블 에 추가된다. [예제 8-28]의 코드는 [예제 7-29]에서 정의한 FormHelper 클래스에 의존한다. 이번 예제는 FormHelper 클래스 코드가 FormHelper.php 라는 파일에 있다고 간주하고, 프로그램 맨 위

쪽의 require 'FormHelper.php' 구문으로 불러온다. 예제 8-28 dishes에 항목을 추가하는 프로그램 <?php // 폼 헬퍼 클래스 불러오기

require 'FormHelper.php'; // 데이터베이스 접속

try { $db = new PDO('sqlite:/tmp/restaurant.db'); catch } (PDOException $e) { print "접속할 수 없습니다: " . $e- > getMessage(); exit(); } // DB 오류와 예외 설정 $db- > setAttribute(PDO::ATTR_ ERRMODE, PDO::ERRMODE_ EXCEPTION); // 페이지의 주 로직: // - 폼이 제출되면, 검증 과정을 수행하고 재표시한다. // - 제출되지 않았으면 폼을 표시한다.

if ($_ SERVER['REQUEST_ METHOD'] = = 'POST') { // validate_ form()이 오류를 반환하면 show_ form()으로 전달한다. list($errors, $input) = validate_ form(); if ($errors) { show_ form($errors); else } { // 제출된 데이터가 올바르면, 처리한다.

process_ form($input);

234 3부 동적 웹사이트 구축 실전


} } else { // 폼이 제출되지 않았으면 폼을 표시한다.

show_ form(); }

function show_ form($errors = array()) { // 기본값 설정. 가격은 $5 $defaults = array('price' = > '5.00'); // 우선시되는 기본값으로 $form 객체 생성 $form = new FormHelper($defaults); // 폼을 표시하는 모든 HTML은 명확성을 위해 분리된 파일에 둔다. include 'insert- form.php'; }

function validate_ form() { $input = array(); $errors = array(); // dish_ name 은 필수값이다. $input['dish_ name'] = trim($_ POST['dish_ name'] ?? ''); if (! strlen($input['dish_ name'])) { $errors[] = '메뉴의 이름을 입력해주세요.'; } // 가격은 올바른 부동소수점 수여야 하며 0보다 커야 한다. $input['price'] = filter_ input(INPUT_ POST, 'price', FILTER_ VALIDATE_ FLOAT);

if ($input['price'] <= 0) { $errors[] = '올바른 가격을 입력해주세요.'; } // is_ spicy의 기본값은'no'다. $input['is_ spicy'] = $_ POST['is_ spicy'] ?? 'no';

return array($errors, $input); }

function process_ form($input) { // 함수 내부에서 전역변수 $db에 접근하기 위해 global로 선언한다. global $db; // $is_ spicy의 값을 checkbox 선택값에 따라 설정한다.

8장 정보 저장: 데이터베이스 235


if ($input['is_ spicy'] = = 'yes') { $is_ spicy = 1; } else { $is_ spicy = 0; } // 신규 메뉴를 테이블에 추가

try { $stmt = $db- > prepare('INSERT INTO dishes (dish_ name, price, is_ spicy) VALUES (?,?,?)'); $stmt- > execute(array($input['dish_ name'], $input['price'],$is_ spicy)); // 사용자에게 메뉴를 추가했음을 알림

print htmlentities($input['dish_ name']) . ' 메뉴가 데이터베이스에 추가되었습니다.'; } catch (PDOException $e) { print "데이터베이스에 메뉴를 추가할 수 없습니다."; } } ?>

[예제 8-28]의 구조는 출력 함수, 검증, 폼 처리, 호출 함수 결정 로직 등 7장의 폼 예제와 기본적으로 같다. 이번에 새로 추가된 부분은 데이터베이스 접속을 설정하는 전역 코드와 process_form()의 데이터베이스 관련 처리 코드다.

데이터베이스 설정 코드는 require 구문과 if ($_SERVER['REQUEST_METHOD'] == 'POST') 구문 사이에 나온다. new PDO() 호출로 데이터베이스 접속을 생성하고, 이후 코드에서 접속이 성공적인지 확인한 뒤 예외 모드와 오류 처리를 설정한다. show_form() 함수는 insert-form.php 파일에 정의된 폼 HTML을 표시한다. [예제 8-29]

은 이 파일의 내용이다. 예제 8-29 dishes에 항목을 추가하는 폼 <form method = "POST" action = "<?= $form- > encode($_ SERVER['PHP_ SELF']) ?> "> <table> <?php if ($errors) { ?> <tr> <td> 다음 항목을 수정해주세요:</td> <td> <ul>

236 3부 동적 웹사이트 구축 실전


<?php foreach ($errors as $error) { ?> <li> <?= $form- > encode($error) ?> </li> <?php } ?> </ul> </td> <?php } ?> <tr> <td> 메뉴 이름:</td> <td> <?= $form- > input('text', ['name' = > 'dish_ name']) ?> </td> </tr> <tr> <td> 가격:</td> <td> <?= $form- > input('text', ['name' = > 'price']) ?> </td> </tr> <tr> <td> 맵기:</td> <td> <?= $form- > input('checkbox', ['name' = > 'is_ spicy',

'value' = > 'yes']) ?> Yes</td>

</tr> <tr> <td colspan = "2" align = "center"> <?= $form- > input('submit', ['name' = > 'save','value' = > '주문']) ?> </td> </tr> </table> </form>

접속 이외의 모든 데이터베이스 상호작용은 process_form() 함수에서 실행된다. 우선 $GLOBALS['db'] 대신 간결하게 $db로 데이터베이스 접속 변수를 참조할 수 있도록 global $db 구문을 실행한다. 다음에 나오는 if() 절은 제출된 $input['is_spicy']의 값에 따라 지역

변수 $is_spicy에 1 또는 0을 할당한다. $input['is_spicy']는 매운맛 여부에 따라 yes 또는 no라는 문자열이 지정되지만 테이블의 is_spicy 컬럼은 매운맛일 경우 1을, 그렇지 않을 경우 0을 저장하기 때문이다.

이후에는 실제로 데이터베이스에 새로운 정보를 넣는 prepare() 와 execute() 를 호출한다. INSERT 쿼리에는 3개의 자리표시자가 있고 각각 $input['dish_name'], $input['price'], $is_spicy 변수로 대체된다. dish_id 컬럼에 해당하는 값은 SQLite가 자동 계산하므로

8장 정보 저장: 데이터베이스 237


지정해줄 필요가 없다. 마지막으로, 메뉴가 추가되었다는 메시지를 사용자에게 보여준다. htmlentities() 함수는 메뉴명에 포함됐을지 모를 HTML 태그나 자바스크립트로부터 페이

지를 보호한다. prepare()와 execute()는 try 블록 내부에 있으므로, 실행 중 문제가 발생하 면 오류 메시지가 출력된다.

8.7 데이터 가져오기 데이터베이스로부터 정보를 가져오려면 query() 메서드로 데이터베이스에 SQL 쿼리를 전달 한다. query()는 쿼리의 결과 로우에 접근할 수 있는 PDOStatement 객체를 반환한다. 이 객체 의 fetch() 메서드는 실행할 때마다 결과 로우를 차례로 반환하며, 남은 로우가 없으면 거짓을 나타내는 값을 반환한다. 이런 작동 방식 덕분에 fetch()는 [예제 8-30]처럼 while() 루프와 결합하기 좋다. 예제 8-30 query()와 fetch()로 로우 가져오기 $q = $db- > query('SELECT dish_ name, price FROM dishes');

while ($row = $q- > fetch()) { print "$row[dish_ name], $row[price] \n"; }

[예제 8-30]의 출력 결과는 다음과 같다.

호두 번, 1 캐슈너트와 양송이버섯, 4.95 말린 오디, 3 칠리소스 가지 볶음, 6.5

while() 루프를 처음 실행하면 fetch() 는 호두 번과 1을 담은 배열을 반환하며 이 배열은 $row에 할당된다. 원소가 있는 배열은 참을 나타내므로 while() 루프의 내부 코드를 실행

하고, SELECT 쿼리가 반환한 첫 번째 로우 데이터부터 출력한다. 이 과정이 세 번 반복된다.

238 3부 동적 웹사이트 구축 실전


while() 루프가 반복될 때마다 fetch()는 SELECT 쿼리가 반환한 로우 집합에서 순서대로 로

우를 반환한다. 반환할 로우가 더는 남아있지 않을 때 fetch()는 거짓을 나타내는 값을 반환 하고 while() 루프가 완료된다. 기본적으로, fetch()가 반환하는 배열은 숫자키와 문자열키를 모두 포함한다. 숫자키는 0부터 시작하며 로우의 각 컬럼값이 할당된다. 각 문자열키는 컬럼명과 같고 마찬가지로 해당하는 컬 럼값이 할당된다. [예제 8-30]에서 $row[dish_name]과 $row[price] 대신 $row[0]와 $row[1] 을 사용해도 결과는 같다. SELECT 쿼리가 반환한 로우의 개수를 알고 싶을 때, 오차 없이 가장 간단하게 확인하는 방법은

쿼리가 반환한 모든 로우를 가져와서 개수를 세는 것이다. PDOStatement 객체는 로우 개수를 반환하는 rowCount() 메서드를 제공하지만, 이 메서드가 작동하지 않는 데이터베이스도 있다. 로우의 양이 비교적 적고 프로그램에서 지속해서 쓰인다면, [예제 8-31]처럼 루프를 사용하지 않고 fetchAll() 메서드로 한 번에 가져와 배열에 넣을 수 있다. 예제 8-31 루프를 사용하지 않고 전체 로우 가져오기 $q = $db- > query('SELECT dish_ name, price FROM dishes'); // $rows는 원소가 4개인 배열이다. // 각 원소는 데이터베이스의 데이터 로우 하나에 해당한다. $rows = $q- > fetchAll();

로우의 양이 너무 많아서 한 번에 가져오는 것이 현실적으로 어렵다면, SQL의 COUNT() 함수를 이용해 로우의 개수를 데이터베이스 프로그램에 요청할 수 있다. 예를 들어 SELECT COUNT(*) FROM dishes 쿼리는 하나의 컬럼값을 반환하며 이 값은 테이블의 전체 로우 개수다.

SQL 수업: SELECT SELECT 명령은 데이터베이스에서 데이터를 가져온다. [예제 8-32]는 SELECT의 문법이다. 예제 8-32 데이터 가져오기

SELECT 컬럼1[, 컬럼2, 컬럼3, ...] FROM 테이블명

8장 정보 저장: 데이터베이스 239


[예제 8-33]의 SELECT 쿼리는 dishes 테이블 전체 로우의 dish_name 컬럼과 price 컬럼을 가져온다. 예제 8-33 dish_name과 price 가져오기

SELECT dish_ name, price FROM dishes

단축 표현으로, 컬럼을 나열하는 대신 *을 사용하면 테이블의 모든 컬럼을 가져온다. [예제

8-34]의 SELECT 쿼리는 dishes 테이블의 모든 컬럼을 가져온다. 예제 8-34 SELECT 쿼리에 * 사용하기

SELECT * FROM dishes

SELECT 구문으로 가져올 로우의 범위를 한정하려면 WHERE 절을 추가한다. SELECT 구문은 WHERE 절에 나열된 조건과 일치하는 로우만 반환한다. WHERE 절은 [예제 8-35]처럼 테이블명 뒤에 사용한다. [예제 8-35 ] SELECT에 의해 반환되는 로우 제한하기

SELECT 컬럼1[, 컬럼2, 컬럼3, ...] FROM 테이블 WHERE 조건식

WHERE 절의 조건식은 로우를 제한하는 조건을 논리적으로 묘사한 표현식이다. [예제 8-36]은 WHERE 절을 포함한 SELECT 쿼리의 예시다. 예제 8-36 특정 메뉴 가져오기 ; 가격이 5.00보다 큰 메뉴

SELECT dish_ name, price FROM dishes WHERE price > 5.00 ; 메뉴명이 정확히 "호두 번"인 메뉴

SELECT price FROM dishes WHERE dish_ name = '호두 번' ; 가격이 5.00보다 크고 10.00 이하인 메뉴

SELECT dish_ name FROM dishes WHERE price > 5.00 AND price <= 10.00 ; 가격이 5.00보다 크고 10.00 이하거나, ; 메뉴명이 정확히 "호두 번"인 메뉴 (가격에 관계 없이)

SELECT dish_ name, price FROM dishes WHERE (price > 5.00 AND price <= 10.00) OR dish_ name = '호두 번'

240 3부 동적 웹사이트 구축 실전


[표 8-3]은 WHERE 절에 사용할 수 있는 연산자들이다. 표 8-3 SQL WHERE 절 연산자 연산자

설명

=

같다(PHP의 ==에 해당)

<>

다르다(PHP의 !=에 해당)

>

크다

<

작다

>=

크거나 같다

<=

작거나 같다

AND

그리고(PHP의 &&에 해당)

OR

또는(PHP의 ||에 해당)

()

우선순위 묶음

쿼리 결과에서 하나의 로우만 사용할 때는 query() 다음에 fetch()를 이어서 호출할 수 있다. [예제 8-37]은 dishes 테이블에서 가장 저렴한 항목을 출력하고자 fetch()를 연쇄적으로 사 용한다. [예제 8-37] 쿼리의 ORDER BY와 LIMIT는 뒤에서 설명할 컬럼 ‘SQL 수업 : ORDER BY 와 LIMIT’에서 설명한다.

예제 8-37 fetch()를 연쇄적으로 사용하기 $cheapest_ dish_ info = $db- > query('SELECT dish_ name, price

FROM dishes ORDER BY price LIMIT 1')- > fetch(); print "$cheapest_dish_info[0], $cheapest_dish_info[1]";

[예제 8-37]의 결과는 다음과 같다.

호두 번, 1

8장 정보 저장: 데이터베이스 241


SQL 수업 : ORDER BY와 LIMIT 8.1절에서 언급했듯이 테이블의 로우 사이에는 내재적인 순서가 없다. 기본적으로 데이터베이 스 서버는 SELECT 쿼리의 반환 로우에 특정한 순서를 부여하지 않는다. 로우가 반환되는 순서를 확실히 강제하려면 ORDER BY 절을 SELECT에 추가한다. [예제 8-38]은 dishes 테이블의 모든 로우를 가격에 대한 오름차순으로 가져온다. 예제 8-38 지정된 순서로 가져오는 SELECT 쿼리

SELECT dish_ name FROM dishes ORDER BY price

내림차순으로 가져오려면 정렬할 컬럼 뒤에 DESC(descending)를 추가한다. [예제 8-39]는

dishes 테이블의 모든 로우를 가격에 대한 내림차순으로 반환한다. 예제 8-39 내림차순 정렬

SELECT dish_ name FROM dishes ORDER BY price DESC

order by에는 여러 컬럼을 지정할 수 있다. 첫 번째 ORDER BY 컬럼의 값이 동일하면 두 번째 컬럼값 순서로 정렬된다. [예제 8-40]의 쿼리는 dishes의 로우를 가격에 대한 내림차순으로 정 렬한다. 만일 가격이 같은 로우가 여럿이면 메뉴명 순서3로 정렬된다. 예제 8-40 복수의 컬럼에 의한 정렬

SELECT dish_ name FROM dishes ORDER BY price DESC, dish_ name

ORDER BY는 테이블에 저장된 로우의 실제 순서를 바꾸지 않으며 (사실 로우 사이에는 아무런 순 서도 없다는 점을 기억하자) 쿼리의 결과만 재정렬한다. 이는 쿼리의 응답에만 영향을 미치는 셈 이다. 만일 여러분이 누군가에게 메뉴판을 건네주며 알파벳 순서로 에피타이저를 읽어달라고 요 청해도, 메뉴판에 출력된 메뉴는 그대로다. 단지 ‘모든 에피타이저를 알파벳 순서로 읽어주세요' 라는 여러분의 요청에 따라 응답이 변화될 뿐이다.

SELECT 쿼리는 WHERE 절과 일치하는 모든 로우를 반환한다(WHERE 절이 없으면 테이블의 모든 로우를 반환한다). 가장 가격이 싼 메뉴를 찾거나 10개의 검색 결과만 출력할 때처럼 특정 개수 의 로우만 가져오려면 쿼리 마지막에 LIMIT 절을 추가한다.

3 옮긴이_ 영문은 알파벳 순서, 한글은 일반적으로 가나다 순서로 정렬된다.

242 3부 동적 웹사이트 구축 실전


[예제 8-41]은 dishes에서 가장 가격이 낮은 로우를 반환한다. 예제 8-41 SELECT로 가져온 로우 개수 제한

SELECT * FROM dishes ORDER BY price LIMIT 1

[예제 8-42]는 dishes의 첫 10개 로우를 메뉴명 순서로 반환한다. 예제 8-42 SELECT로 가져온 정렬된 로우 개수 제한

SELECT dish_ name, price FROM dishes ORDER BY dish_ name LIMIT 10

일반적으로 LIMIT는 ORDER BY와 함께 사용해야 한다. ORDER BY가 없으면 데이터베이스는 임 의의 순서로 로우를 반환한다. 따라서 한 번 실행했던 쿼리의 ‘첫 번째’ 로우는, 같은 쿼리를 다시 실행했을 때는 ‘첫 번째’ 로우가 아닐 수도 있다.

8.8 반환 결과 형식 변경 fetch()는 데이터베이스로부터 로우를 가져와 숫자키와 문자열키 배열을 조합해서 반환한다.

이런 방식은 큰따옴표 문자열 내부에 편리하게 값을 삽입할 수 있다는 장점이 있지만, 단점도 존재한다. 숫자키를 쓸 때는 원하는 값이 SELECT 쿼리의 반환 배열에서 몇 번째 원소인지 기억 해야 하는데, 원소가 많으면 일일이 기억하기 어렵고 오류가 생기기도 쉽다. 문자열키를 이용할 때는 컬럼명에 따라 특별히 따옴표 처리를 해야 할 경우도 있다. 다행 히 PDO는 결과 로우를 가져오는 방식을 여러 가지 중 하나로 지정할 수 있다. fetch() 나 fetchAll() 의 첫 번째 인수에 원하는 방식을 전달하면 숫자키 배열, 문자키 배열, 객체 중 하나의 형식으로 로우를 가져올 수 있다. 숫자키 배열로 로우를 가져오려면 fetch() 나 fetchAll() 함수의 첫 번째 인수에 PDO::FETCH_NUM을 전달한다. 문자열키 배열로 가져오려

면 PDO::FETCH_ASSOC를 전달한다(문자열키 배열은 ‘연관’ 배열로도 불린다는 점을 기억하자). 배열 대신 객체 형식으로 가져오려면 PDO::FETCH_OBJ를 사용한다. 반환되는 로우의 각 컬럼값 은 객체의 속성값으로 할당되며 속성명은 컬럼명과 같다.

8장 정보 저장: 데이터베이스 243


다. 이 파일은 대부분의 유닉스 시스템에서 사용자 계정의 목록이 담긴 파일이다. /usr/local/

data/../../../etc/passwd 라는 파일명은 /usr/local/data 디렉터리에서 한 단계 상위(/usr/ local )로 올라가고, 그 곳에서 다시 상위(/usr )로, 또 다시 상위(/, 최상위 파일시스템)로 올 라간 뒤 /etc 로 내려와 passwd 파일을 가리킨다. 이러한 규칙이 PHP 프로그램에서 어떤 문제를 일으킬 수 있을까? 폼으로부터 받은 데이터를 적절한 예방 조치 없이 파일명에 그대로 사용하면 파일시스템 접근 권한을 탈취하려는 공격에 무방비 상태로 노출된다. [예제 9-21]은 제출된 폼 매개변수의 값을 파일명에 사용하기 전에 슬래시와 .. 문자를 모두 제거하는 방식으로 위험을 예방한다. 예제 9-21 파일명에 들어갈 폼 매개변수를 안전하게 조치하기 // user에서 슬래시 제거하기 $user = str_ replace('/', '', $_ POST['user']); // user에서 .. 제거하기 $user = str_ replace('..', '', $user);

if (is_ readable("/usr/local/data/$user")) { print '사용자 정보: ' . htmlentities($user) .': <br/> '; print file_ get_ contents("/usr/local/data/$user"); }

사용자가 [예제 9-21]에서 user 폼 매개변수로 ../../../etc/passwd라는 값을 제출하더라도 이 값은 file_get_contents() 함수에 전달되기 전에 etcpasswd로 변환된다. realpath() 함수는 사용자가 입력한 값에서 지저분한 요소를 제거하는 유용한 도구 중 하나

다. ..가 포함된 파일명은 쉽게 눈에 들어오지 않고 혼동을 유발할 수 있지만 realpath()는 ..를 해석하고 변환해 파일의 위치를 더 직접적으로 나타낸다. 예를 들어 realpath('/usr/ local/data/../../../etc/passwd')를 실행하면 /etc/passwd라는 문자열을 반환한다. [예제

9-22]처럼 폼 데이터를 삽입한 후 realpath()를 실행하면 파일명이 적절한지 확인할 수 있다. 예제 9-22 realpath()로 파일명을 안전하게 처리하기 $filename = realpath("/usr/local/data/$_ POST[user]"); // $filename이 /usr/local/data 하위에 있는지 확인하기

274 3부 동적 웹사이트 구축 실전


if (('/usr/local/data/' = = substr($filename, 0, 16)) && is_ readable($filename)) { print '사용자 정보: ' . htmlentities($_ POST['user']) .': <br/> '; print file_ get_ contents($filename); } else { print "존재하지 않는 사용자입니다."; }

[예제 9-22]에서 $_POST['user']가 james라면 $filename은 /usr/local/data/james가 되 고 if() 코드 블록이 실행된다. $_POST['user']가 ../secrets.txt처럼 수상쩍은 값이더라도 $filename이 /usr/local/secrets.txt로 변환되므로 if() 조건식을 만족하지 못하고 ‘존재 하지 않는 사용자입니다.’가 출력된다.

9.8 마치며 이번 장에서 다룬 내용을 정리하면 다음과 같다. PHP 엔진의 파일 접근 권한 기준

●●

file_get_contents ( )로 파일 전체 읽기

●●

file_put_contents ( )로 파일 전체 쓰기

●●

file ( )로 파일의 모든 줄을 배열로 얻기

●●

fopen ( )과 fclose ( )로 파일 열고 닫기

●●

fgets ( )로 파일을 한 줄 읽기

●●

feof ( )와 while ( ) 루프로 파일을 한 줄씩 읽기

●●

모든 운영체제에서 작동하도록 파일명에 슬래시 사용하기

●●

fopen ( )에 사용하는 다양한 모드

●●

fwrite ( )로 파일에 데이터 쓰기

●●

fgetcsv ( )로 CSV 파일을 한 줄 읽기

●●

fputcsv ( )로 CSV 파일에 한 줄 쓰기

●●

php ://output 스트림을 이용해 출력하기

●●

file_exists ( )를 이용해 파일의 존재 여부 확인하기

●●

is_readable ( )과 is_writeable ( )을 이용해 파일 사용 권한 확인하기

●●

9장 파일 다루기 275


파일 접근 함수가 반환하는 오류 확인

●●

삼중등호 연산자( = = = )로 반환값 체크하기

●●

외부로부터 제공된 파일명의 잠재적인 위험 요소 제거하기

●●

9.9 연습문제 HP 엔진을 사용하지 않고 [예제 9-1]의 형식으로 신규 템플릿 파일을 생성하라. file_ 1. P get_contents()와 file_put_contents()를 사용해 HTML 템플릿 파일을 읽고, 템플릿

변수를 출력값으로 교체한 다음, 별도의 파일로 페이지를 저장하는 프로그램을 작성하라. HP 엔진을 사용하지 않고 이메일 주소 목록 파일을 addresses.txt 라는 이름으로 생성하 2. P 라. 주소는 한 줄에 하나씩 넣고 몇몇 주소는 한 번 이상 넣는다. addresses.txt 파일을 한 줄씩 읽고 각 이메일 주소가 몇 번씩 나오는지 세는 PHP 프로그램을 작성하라. 각 이메일 주소와 출현 횟수는 별도의 addresses-count.txt 파일에 기록한다. addresses-count.

txt 파일의 한 줄은 각 이메일 주소의 출현 횟수와 이메일 주소를 쉼표로 연결한 문자열이 며. addresses.txt 파일에 가장 많이 등장하는 주소부터 내림차순으로 기록한다. SV 파일을 HTML 테이블로 출력하라. 적당한 CSV 파일(혹은 스프레드시트 프로그램)이 3. C 없다면 [예제 9-9]의 데이터를 사용하라.

4. 파일명 입력폼을 출력하는 PHP 프로그램을 작성하라. 파일이 서버에 존재하고 읽을 수 있 으며 웹 서버의 문서 루트 디렉터리 하위에 있으면 파일의 내용을 출력하라. 예를 들어, 사 용자가 article.html 을 입력하면 문서 루트 디렉터리 하위의 article.html 을 출력하라. 만 일 사용자가 catalog/show.php 를 입력하면, 문서 루트 디렉터리 하위의 catalog 디렉터 리에 있는 show.php 파일의 내용을 출력하라. [표 7-1]을 참고하면 웹 서버의 문서 루트 디렉터리를 확인할 수 있다.

5. 4 번 문제의 답안을 수정하여 이름이 .html 로 끝나는 파일만 출력되게 하라. PHP 소스 코드 를 사용자에게 노출하는 것은 위험하다. 사용자가 입력한 파일이 데이터베이스 사용자명과 암호처럼 민감한 정보가 담긴 페이지일 수도 있다.

276 3부 동적 웹사이트 구축 실전


CHAPTER

10

사용자 추적: 쿠키와 세션

웹 서버를 식료품점의 직원에 비유해 보자. 이 가게는 극성스런 손님들로 가득 차 늘 분주하다. 손님들은 “양념 고기 반 근이요!”, “대패 삼겹살 한 근이요!” 등의 주문을 저마다 외쳐대고, 직 원은 바쁘게 오가며 고기를 썰고 포장해 주문을 처리한다. 고객의 역할은 웹 클라이언트가 맡 는다. 웹 클라이언트가 전자적으로 요청 주문을 외치면(“/catalog/yak.php 부탁해요!”, “여 기 폼 제출합니다!” 등등) 서버는 전자적으로 이리저리 움직이며 PHP 엔진의 도움을 받아 그 에 상응하는 응답을 생성한다. 웹 서버와 달리 식료품점의 직원에게는 기억력이라는 유용한 능력이 있다. 직원은 특정 고객과 그 고객의 요청을 자연스럽게 결부시킬 수 있지만, PHP 엔진과 웹 서버는 어떤 특수한 조치 없이는 이런 능력을 발휘할 수 없다. 이 점이 쿠키cookies가 필요한 이유다. 웹 서버와 PHP 엔진은 쿠키를 통해 웹 클라이언트를 식별할 수 있다. 웹 클라이언트는 요청을 생성할 때마다 쿠키를 함께 실어 보낸다. 엔진이 쿠키를 읽고 이전의 요청에 동봉됐던 쿠키와 같 은 쿠키임을 확인하면, 해당 요청 역시 이전과 같은 웹 클라이언트에서 들어왔음을 알게 된다. 고객 역시 이와 같은 전략을 써야 할 때가 있다. 점원의 기억력이 신통치 않으면 고객은 다음과 같은 형태로 주문해야 한다. “번호표 56입니다. 양념 소고기 반 근 주세요.” “번호표 29입니다. 고로케 세 개 주세요.” “번호표 56입니다. 훈제 소고기 두 근 주세요.”

10장 사용자 추적: 쿠키와 세션 277


“번호표 77입니다. 호밀빵 환불 부탁해요. 상했네요.” “번호표 29입니다. 양념 소시지 주세요.”

이처럼 “번호표 몇번입니다. 무엇무엇을 주세요”라는 요청이 바로 쿠키가 담당하는 부분이다. 쿠키는 특정 고객과 요청 상품을 연결짓는 역할을 한다. 쿠키는 이름(“customer” 등)과 값(“77”, “ronald” 등)으로 구성된다. 이어지는 절에서는 프 로그램에서 개별적인 쿠키를 설정하고 읽고 삭제하는 방법을 알려준다. 단일 쿠키는 하나의 정보를 지속해서 유지할 때 가장 유용하다. 가끔 사용자에 대한 여러 가지 정보(장바구니의 내용 등)를 지속해서 유지해야 할 때가 있는데, 복수의 쿠키는 이런 작업을 하기에는 크고 무거울 뿐만 아니라 다루기도 어렵다. 이 문제를 풀려면 PHP의 세션session을 활 용해야 한다. 세션은 쿠키를 이용해 사용자를 식별하며 각 사용자의 데이터 모음을 서버에 임시 저장한다. 이 데이터는 요청이 반복되는 동안 꾸준히 유지된다. 먼저 하나의 요청에서 사용자의 세션에 (장바구니에 상품을 추가하는 것처럼) 변수를 추가하고, 이어지는 요청에서 (결제 페이지처럼 장바구니의 모든 목록을 읽을 필요가 있을 때) 세션에 있는 값을 읽어올 수 있다. 10.2절에서 세션을 시작하는 방법을 설명하고 10.3절에서는 세션을 다루는 방법에 대해 상세히 알아본다.

10.1 쿠키 다루기 쿠키를 설정하려면 setcookie() 함수를 사용한다. 이 함수를 호출하면서 쿠키명과 값을 지 정하면 웹 클라이언트가 이를 기억해 두었다가 이어지는 요청에서 서버로 되돌려준다. [예제

10-1]은 userid 쿠키에 ralph라는 값을 저장하는 코드다. 예제 10-1 쿠키 설정

setcookie('userid','ralph');

이전에 PHP 프로그램을 통해 생성했던 쿠키를 읽으려면 자동 전역변수인 $_COOKIE 배열을 사

278 3부 동적 웹사이트 구축 실전


용한다. [예제 10-2]는 userid 쿠키의 값을 출력한다. 예제 10-2 쿠키 값 출력

print 'Hello, ' . $_COOKIE['userid'];

setcookie() 함수를 통해 쿠키에 넣을 수 있는 값은 문자열이나 숫자다. 배열 또는 복잡한 데

이터 구조는 넣을 수 없다. TIP

setcookie() 함수는 웹 클라이언트에 쿠키를 전송하기 전에 쿠키값을 URL 인코딩 방식으로 변환한 다. 이 인코딩은 공백을 +로 변환하고 글자, 숫자, 밑줄문자, 하이픈, 마침표가 아닌 모든 글자를 퍼센트 기호와 16진수 아스키 값으로 변환한다. PHP가 쿠키값을 변경하지 않게 하려면 setcookie() 대신

setrawcookie()를 사용한다. 그러나 setrawcookie()를 사용하면 =와 ,, ;를 비롯해 그 어떤 화이트 스페이스도 쿠키 값에 사용할 수 없다.

setcookie()를 호출하면 PHP 엔진이 웹 클라이언트에 되돌려줄 응답을 생성할 때 특수한 헤

더가 추가된다. 이 헤더는 새로 생성할 쿠키와 관련한 정보를 담고 있다. 새로 생성된 쿠키의 이름과 값은 웹 클라이언트가 그 다음 요청을 통해 서버로 되돌려준다. 다음 [그림 10-1]은 이 두 단계의 대화를 나타낸다. 그림 10-1 쿠키를 설정할 때 클라이언트와 서버의 통신 과정

첫 번째 요청 웹 클라이언트

웹 서버 /catalog.php 페이지 부탁합니다.

1. “userid” 쿠키에 “ralph” 저장 2. 페이지 여기 있습니다.

두 번째 요청 웹 클라이언트

웹 서버 /shopping-cart.php 페이지 부탁합니다. userid = ralph

페이지 여기 있습니다. ralph님.

10장 사용자 추적: 쿠키와 세션 279


일반적으로 setcookie()는 페이지가 아직 아무런 출력을 하지 않았을 때 호출해야 한다. 이 는 모든 출력( print ) 구문이 반드시 setcookie() 보다 나중에 나와야 한다는 의미다. 또한 setcookie() 함수를 실행하기 전에는 페이지의 시작 태그인 <?php 앞에 어떠한 텍스트도 있

어서는 안 된다. 추후 10.6절에서는 왜 이런 요구조건이 존재하는지, 여러 상황별로 어떻게 대 처해야 하는지 설명한다. 다음 [예제 10-3]은 올바른 방법으로 페이지 맨 위에서 setcookie()를 호출한다. 예제 10-3 setcookie()로 페이지 시작하기 <?php

setcookie('userid','ralph'); ?> <html> <head> <title> 쿠키 사용 페이지</title> </head> <body>

setcookie()가 있는 PHP 블록이 모든 HTML보다 먼저 나오기 때문에 이 페이지는 쿠키가 올바르게 설정됩니다. </body> </html>

쿠키 정보는 웹 클라이언트가 쿠키 정보가 담긴 요청을 보냈을 때만 $_COOKIE에 저장된다. 다시 말해, setcookie() 를 호출한다 해도 그 즉시 쿠키 값이 $_COOKIE에 저장되지 않는다 는 의미다. 웹 클라이언트는 쿠키 설정이 포함된 응답을 완전히 받아들이고 나서야 쿠키에 대 해 알게 되고, 이어지는 요청에서 클라이언트가 쿠키를 되돌려 보내면 비로소 쿠키 정보가 $_ COOKIE에 저장된다.

기본적으로 쿠키의 수명은 웹 클라이언트의 수명을 따른다. 즉 웹 브라우저를 종료하면 쿠키는 삭제된다. 쿠키가 더 길게(혹은 더 짧게) 살아남게 하려면 setcookie()의 세 번째 인수인 쿠 키 만료 시각을 지정한다. 이 인수는 선택적이다. [예제 10-4]는 다양한 쿠키 만료 시각을 보 여준다. 예제 10-4 쿠키 만료 시각 설정 // 지금부터 한시간 이후 만료되는 쿠키

setcookie('short- userid','ralph',time() + 60*60); // 지금부터 24시간 이후 만료되는 쿠키

280 3부 동적 웹사이트 구축 실전


setcookie('longer- userid','ralph',time() + 60*60*24); // 2019년 10월 1일 정오에 만료되는 쿠키 $d = new DateTime("2019- 10- 01 12:00:00");

setcookie('much- longer- userid','ralph', $d- > format('U'));

setcookie()에 쿠키의 만료 시각을 지정할 때, 1970년 1월 1일 자정으로부터 만료 시각까지

경과된 시간을 초로 환산해 표현한다. time() 함수와 DateTime::format()의 U 형식을 결합 하면 이 값을 적절하게 표현할 수 있다1. time() 함수는 (유닉스 시간이라고도 부르는) 1970 년 1월 1일 이후 현재까지 경과된 시간을 초로 환산해 반환한다. 따라서 현재 시각부터 일정 한 시간이 흐른 뒤 쿠키가 만료되게 하려면 그 기간을 초로 환산한 뒤 time()이 반환하는 값에 더한다. 60초는 1분, 60분은 1시간으로 60 * 60은 1시간 동안 흐른 초의 양이므로, 만료 시각 을 time() + 60 * 60으로 지정하면 지금부터 1시간이 지난 뒤 쿠키가 삭제된다. 같은 원리로, time() + 60 * 60 * 24초는 지금부터 하루가 지난 시점을 의미한다. DateTime::format()의 형식 문자 중 U는 DateTime 객체가 표현하는 시각을 초로 환산해 반

환한다. 쿠키의 만료 시각을 지정하면 쿠키는 웹 클라이언트가 종료되거나 재시작되어도 그대 로 유지된다. 또한 만료 시각 외에도 setcookie()에는 경로, 도메인, 2가지 보안 매개변수 등 유용한 매개변수가 있다. 일반적으로 쿠키는 쿠키를 설정했던 페이지와 같은(또는 하위) 디렉터리에 있는 페이지를 요 청할 때 함께 전송된다. 예를 들어 http://www.example.com/buy.php 에 설정된 쿠키는 www.example.com 서버에 대한 모든 요청에 함께 전송된다. buy.php 가 웹 서버의 최상위 디

렉터리에 있기 때문이다. 조금 더 살펴보자. http://www.example.com/catalog/list.php 에 설정된 쿠키는 http://

www .example .com /catalog /search .php 나 http ://www .example .com /catalog / detailed/search.php 처럼 catalog 디렉터리 하위 파일에 대한 요청에는 전송된다. 하지만 http://www.example.com/sell.php 나 http://www.example.com/users/profile. php 처럼 catalog 디렉터리 밖이나 상위 페이지 요청에는 전송되지 않는다. URL에서 호스트명 뒷부분은 경로path라 불린다. 에를 들면 /buy.php 나 /catalog/list.php , 1 time()과 DateTime은 15장에서 자세히 알아본다.

10장 사용자 추적: 쿠키와 세션 281


/users/profile.php 등이다. 쿠키를 설정한 경로와 일치하지 않는 다른 경로에도 쿠키를 보내 려면 setcookie()의 네 번째 인수로 허용 경로를 전달한다. 가장 유연한 허용 경로는 /로, ‘서 버에 전송하는 모든 요청에 대해 쿠키를 허용하라’는 의미다. 다음 [예제 10-5]는 쿠키 경로를 /로 설정한다.

예제 10-5 쿠키 경로 설정

setcookie('short- userid','ralph',0,'/');

[예제 10 -5 ]는 setcookie( ) 의 만료 시각 인수로 0 을 사용한다. 이렇게 설정하면 setcookie() 메서드는 기본 만료 시각(웹 클라이언트가 종료될 때)으로 쿠키를 설정한다. setcookie()에 경로를 지정하면 만료 시각 인수를 채워넣어야 한다. time() + 60*60와 같은

특정한 값을 사용해 특정 시각을 지정하거나, 0을 사용해 기본 만료 시각을 지정할 수 있다. 공용 서버를 사용하거나 모든 페이지가 특정 디렉터리 하위에 있을 경우에는 허용 경로를 / 보다 자세히 설정하면 좋다. 예를 들어 웹 문서 디렉터리가 http ://students .example .

edu/~alice/ 하위에 있다면 [예제 10-6]처럼 쿠키 경로를 /~alice/로 설정해야 한다. 예제 10-6 쿠키 경로를 특정 디렉터리로 설정하기

setcookie('short- userid','ralph',0,'/~alice/');

쿠키 경로를 /~alice/로 설정하면 short-userid 쿠키는 http ://students .example .

edu/~alice/search.php 페이지를 요청할 때는 전송된다. 하지만 다른 사용자의 디렉터리 에 있는 웹페이지를 요청할 때는 전송되지 않는다. 예를 들면 http://students.example.

edu/~bob/sneaky.php 나 http://students.example.edu/~charlie/search.php 와 같은 웹페이지다. 다섯 번째 인수는 웹 클라이언트가 특정 쿠키를 보내도록 허용할 도메인이다. 이 인수에 아무 값도 넣지 않으면 쿠키를 설정했던 호스트와 같은 호스트의 요청에만 쿠키를 전송한다. 예를 들어 http://www.example.com/login.php 에서 쿠키를 설정했다면 www.example.com 서 버에 대한 다른 요청들에도 쿠키를 전송하지만, shop.example.com이나 www.yahoo.com, www.

282 3부 동적 웹사이트 구축 실전


example.org 등으로는 보내지 않는다.

이런 제한 조건을 약간 변경할 수 있다. 웹 클라이언트는 요청을 보낼 때 setcookie()의 다섯 번째 인수와 요청 호스트를 비교하는데, 호스트의 마지막 부분이 인수값와 일치하면 쿠키를 전 송한다. 보편적인 사용 방식은 쿠키 도메인 인수를 .example.com과 같은 형식으로 지정하는 것이다(구형 웹 클라이언트에는 시작 마침표가 중요하다). 이 설정에 의해 웹 클라이언트는 www.example.com, shop.example.com, testing.development.example.com 등 .example. com으로 끝나는 서버로 요청을 보낼 때 쿠키를 전송한다. 다음 [예제 10-7]은 이런 방식으로

쿠키 도메인을 설정한다. 예제 10-7 쿠키 도메인 설정

setcookie('short- userid','ralph',0,'/','.example.com');

[예제 10-7]의 쿠키는 웹 클라이언트가 종료될 때 만료되며, 웹 클라이언트가 요청을 보내는 대상이 .example.com으로 끝나는 모든 서버의 모든 디렉터리에 해당될 때(경로가 /이므로) 함께 전송된다. setcookie()에 지정하는 도메인은 서버명의 끝부분과 반드시 일치해야 한다. PHP 프로그램

이 호스팅되는 서버가 students.example.edu라면, 설령 쿠키 도메인을 .yahoo.com으로 지정 한다 해도 그 쿠키가 yahoo.com 도메인의 모든 서버로 전송되지는 않는다. 하지만 쿠키 도메인 을 .example.edu으로 설정한다면 example.edu 도메인의 모든 서버로 쿠키를 전송할 수 있다. setcookie()의 마지막 두 인수는 선택적이며 쿠키의 보안에 영향을 미친다. setcookie()의

여섯 번째 인수에 true 값을 전달하면, 웹 클라이언트는 오직 URL이 https로 시작하는 보안 연결 요청에만 쿠키를 전송한다. 이 경우 setcookie()를 실행하는 페이지에 대한 요청이 보안 연결을 통해서만 이루어지는지 확인해야 하며, 이어지는 요청이 보안 연결이 아닐 경우에는 쿠 키가 전송되지 않는다는 점에 유의해야 한다. 마지막으로, setcookie()의 일곱 번째 인수로 true를 전달하면 웹 클라이언트가 HttpOnly 쿠키를 생성한다. HttpOnly 쿠키는 일반 쿠키처럼 클라이언트와 서버 사이를 오가지만, 클라 이언트의 자바스크립트가 접근할 수 없다. 이 설정은 크로스 사이트 스크립팅 공격으로부터 쿠 키를 보호한다(7.4.6절 참고). 다음 [예제 10-8]의 쿠키는 24시간 후 만료되고, 경로나 도메

10장 사용자 추적: 쿠키와 세션 283


인 제한이 없으며, 보안 연결을 통해서만 전송되고, 클라이언트 사이드 자바스크립트에서 사용 할 수 없다. 예제 10-8 보안 매개변수로 쿠키 설정하기 // 도메인과 경로에 null을 넣으면 // PHP가 쿠키를 설정할 때 도메인과 경로에 제한을 두지 않는다.

setcookie('short- userid','ralph',0,null, null, true, true);

쿠키를 지우려면 [예제 10-9]처럼 지우려는 쿠키의 이름을 setcookie()에 넣고 쿠키 값에 빈 문자열을 넣는다. 예제 10-9 쿠키 삭제

setcookie('short- userid','');

만료 시각이나 도메인을 기본값이 아닌 특정한 값으로 설정한 쿠키는 삭제할 때도 동일한 설정 값을 사용해야 올바르게 삭제된다. 대부분의 경우 쿠키의 만료 시각, 경로, 도메인을 기본값으로 설정해도 무난하다. 하지만 이 값 들을 바꿨을 때 쿠키가 어떻게 작동하는지 이해하면, PHP 세션의 작동 방식과 조작 방법을 이 해하는 데 도움이 된다.

10.2 세션 활성화 기본적으로 세션은 PHPSESSID라는 쿠키를 사용한다. PHP 엔진은 페이지에서 세션을 시작할 때 이 쿠키가 있는지 확인하고 존재하지 않으면 생성한다. PHPSESSID 쿠키의 값은 임의의 알 파벳과 숫자가 섞인 문자열이다. 각 웹 클라이언트는 서로 다른 세션 아이디를 부여받는다. 서 버는 PHPSESSID 쿠키에 담긴 세션 아이디를 통해 고유한 웹 클라이언트들을 식별하고, 이를 통해 엔진은 각 웹 클라이언트 별로 데이터 집합을 분리하고 유지한다.

284 3부 동적 웹사이트 구축 실전


[그림 10-2]는 세션을 시작할 때 웹 클라이언트와 서버 사이에 오가는 대화를 나타낸다. 그림 10-2 세션 시작 시 클라이언트와 서버의 통신

첫 번째 요청 웹 서버

웹 클라이언트 /shopping-cart.php 페이지 부탁합니다.

1. “PHPSESSID” 쿠키에 “d41d8c8427e” 저장 2. 페이지 여기 있습니다.

두 번째 요청 웹 클라이언트

웹 서버 /buy.php?item_id = 6432 페이지 부탁합니다. PHPSESSID = d41d8c8427e

페이지 여기 있습니다. ralph님.

페이지에 세션을 사용하려면 스크립트가 시작되는 지점에서 session_start() 를 호출한다. 이 함수는 setcookie()처럼 출력이 전송되기 전에 호출해야 한다. session.auto_start를 On 으로 설정하면 모든 페이지에서 세션을 사용할 수 있으며 일일이 session_start()를 호출할 필요가 없다. 설정을 변경하는 방법은 부록 A에서 설명한다.

10.3 정보 저장과 확인 세션 데이터는 자동 전역변수인 $_SESSION 배열에 저장된다. 이 배열의 원소들을 읽거나 변경 하는 방식으로 세션 데이터를 다룰 수 있다. [예제 10-10]은 $_SESSION 배열을 사용해 특정 페이지를 사용자가 몇 번 봤는지 지속해 추적하며, 현재까지의 열람 횟수를 알려준다. 예제 10-10 세션을 이용해 페이지 접근 횟수 세기

session_ start(); if (isset($_ SESSION['count'])) {

10장 사용자 추적: 쿠키와 세션 285


$_ SESSION['count'] = $_ SESSION['count'] + 1; } else { $_ SESSION['count'] = 1; }

print "당신은 이 페이지를 " . $_SESSION['count'] . '번 보셨습니다.';

사용자가 [예제 10-10]의 페이지에 처음 접근했을 때 웹 클라이언트는 PHPSESSID 쿠키를 전 송하지 않는다. session_start() 함수는 사용자에 대한 새로운 세션을 생성하고 PHPSESSID 쿠키에 새로운 세션 아이디를 담아 클라이언트로 전송한다. 세션이 생성되면 $_SESSION 배열은 빈 배열로 시작하므로, $_SESSION 배열에 count 키가 있 는지 확인한 뒤 키가 존재하면 값에 1을 더한다. 그렇지 않으면 첫 번째 방문이므로 count의 값을 1로 설정한다. print 구문의 출력 결과는 다음과 같다.

당신은 이 페이지를 1번 보셨습니다.

요청 마지막 단계에서 $_SESSION의 정보가 웹 서버에 파일로 저장된다. 이 파일은 세션 아이 디별로 생성된다. 다음번부터 사용자가 페이지에 접근하면 웹 클라이언트는 PHPSESSID 쿠키를 전송한다. session_start() 함수는 쿠키를 통해 세션 아이디를 알아내고 해당 아이디의 정보가 저장된

파일을 읽는다. 앞선 예제의 경우 저장된 내용은 $_SESSION['count']가 1이라는 정보뿐이다. 다음으로 $_SESSION['count']가 2로 증가하고 ‘당신은 이 페이지를 2번 보셨습니다.’가 출력 된다. 이전처럼 요청 마지막 단계에서는 $_SESSION['count']가 2가 되었다는 내용이 파일로 저장된다.

PHP 엔진은 $_SESSION의 내용을 세션 아이디별로 유지한다. 프로그램이 실행되는 동안 $_ SESSION은 오직 하나의 세션 데이터를 담는데, 이 데이터는 PHPSESSID 쿠키에 담긴 세션 아이

디에 대응하는 데이터다. 이 세션을 활성화된 세션이라 한다. 각 사용자의 PHPSESSID 쿠키는 모두 서로 다른 값을 저장한다. 페이지 맨 위에서 session_start()를 호출하는 한(또는 session.auto_start가 On인 한), 페이지에서 사용자의 세션 데이터에 접근할 수 있다. $_SESSION 배열은 페이지 간에 정보를

286 3부 동적 웹사이트 구축 실전



Turn static files into dynamic content formats.

Create a flipbook
Issuu converts static files into: digital portfolios, online yearbooks, online catalogs, digital photo albums and more. Sign up and create your flipbook.