도찐개찐
[PHP] PDO 사용법 본문
■ PDO(PHP Data Objects) 란?
-. PDO(PHP Data Objects)는 여러 종류의 데이터베이스를 같은 방식으로 사용할 수 있게 해준다.
-. 그리고 PDOStatement와 데이터 바인딩을 통해 SQL 인젝션을 막아주고, 성능을 향상해준다.
■ PDO를 사용하면 좋은 점
-. PDO(PHP Data Objects)는 같은 방법으로 여러 데이터베이스에 접근할 수 있게 해주는 PHP 확장 모듈입니다.
(PHP는 데이터 베이스별로 확장 모듈을 가지고 있다.)
-. PDO를 사용하면 MySQL, Oracle, MS SQL, PostgreSQL을 포함한 12개의 데이터 베이스를 같은 방식으로 다룰 수 있게 된다.
# 소스코드
<?php $dbHost = "localhost"; // 호스트 주소(localhost, 120.0.0.1) $dbName = "test_db"; // 데이타 베이스(DataBase) 이름 $dbUser = "tester"; // DB 아이디 $dbPass = "1q2w3e"; // DB 패스워드 // PDO + MariaDB 연결하기 $pdo = new PDO("mysql:host={$dbHost};dbname={$dbName}", $dbUser, $dbPass); $statement = $pdo -> query("SELECT CURDATE() AS date FROM DUAL"); $row = $statement -> fetch(PDO::FETCH_ASSOC); echo htmlentities($row['date']); ?> |
# 출력결과
-. PDO를 사용하는 또 다른 이유는 준비 구문(Prepare Statements)을 활용할 수 있기 때문이다.
준비 구문을 사용하면 SQL 인젝션 공격을 막을 수 있고, 애플리케이션의 성능이 향상된다.
-. SQL 인젝션 취약점은 사용자 입력값과 함께 동적으로 쿼리를 만들 때 발생한다.
<?php $query = “UPATE tasks SET name = ‘{$name}’ WHERE id = ‘{$args[’id’]}’”; ?> |
-. 사용자가 입력한 $name과 $args[’id’]를 포함해서 동적으로 쿼리를 만들고 있다.
-. $name의 값에 해킹됐음’— 이 들어오면 ‘;—가 데이터가 이닌 SQL 코드로 인식되어 모든 데이터의 name 컬럼이 해킹됐음으로 변경된다.
<?php $query = “UPATE tasks SET name = ‘해킹됐음’;—’ WHERE id = ‘{$args[’id’]}’”; ?> |
-. 준비 구문을 이용할 떄는 우선 SQL 코드를 정의하고 나중에 파라미터를 대입한다.
<?php $stmt = $pdo -> prepare(”UPDATE tasks SET NAME = :name WHERE id = :id”); $stmt = bindParam(”:name”, $name); ?> |
-. 데이터베이스가 SQL 코드와 데이터를 명확히 구분할 수 있기 떄문에, 준비 구문을 쓰는 것만으로도 SQL인젝션 공격이 막힌다.
-. 준비 구문을 만들고 값만 바꿔가며 여러 번 실행하는 경우 클라이언트와 서버 양쪽에서 쿼리 계획과 메타 정보를 캐시할 수 있게 되어
애플리케이션 성능이 향상된다.
■ PDO 사용법
# 소스코드
<?php $dbHost = "localhost"; // 호스트 주소(localhost, 120.0.0.1) $dbName = "test_db"; // 데이타 베이스(DataBase) 이름 $dbUser = "tester"; // DB 아이디 $dbPass = "1q2w3e"; // DB 패스워드 $dbChar = "utf8"; // 문자 인코딩 // PDO 객체 생성 & DB 접속 $dsn = "mysql:host={$dbHost};dbname={$dbName};charset={$dbChar}"; $pdo = new PDO($dsn, $dbUser, $dbPass); // 쿼리를 담은 PDOStatement 객체 생성 $stmt = $pdo -> prepare("SELECT * FROM girl_group WHERE name = :name"); // PDOStatement 객체가 가진 쿼리의 파라메터에 변수 값을 바인드 $stmt -> bindValue(":name", "나연"); // PDOStatement 객체가 가진 쿼리를 실행 $stmt -> execute(); // PDOStatement 객체가 실행한 쿼리의 결과값 가져오기 $row = $stmt -> fetch(); echo "<pre>"; print_r($row); echo "</pre>"; ?> |
# 출력결과
#01. 데이터베이스 연결
데이터베이스에 접속하기 위해서는 PDO 객체를 생성해야 한다. PDO 객체를 생성할 때는 데이터 소스 네임(DSN, Data Source Name)과
데이터 베이스 유저명 패스워드를 인자로 넘겨준다.
<?php $pdo = new PDO($dsn, $dbUser, $dbPass); ?> |
일반적으로 DSN은 PDO 드라이버명:PDO 드라이버별 접속 문법의 형태로 구성된다.
각 PDO 드라이버별 접속 방법은 PDO 드라이버를 참고하면 된다.
<?php $dsn = "mysql:host={$dbHost};dbname={$dbName};charset={$dbChar}"; ?> |
#02. 쿼리 준비
쿼리 준비는 PDO::prepare 메소드를 이용한다.
PDO::prepare 메소드는 PDOStatement 객체를 반환한다.
<?php $pdoStatement = $pdo -> prepare("SELECT * FROM member WHERE name = :name OR email = :email"); ?> |
<?php $name = "이름"; $email = "이메일"; // 이름 붙인 파라미터를 사용할 때 $pdoStatement = $pdo -> prepare("SELECT * FROM member WHERE name = :name OR email = :email"); $pdoStatement = bindValue(":name", $name); $pdoStatement = bindValue(":email", $email); // 물음표를 사용할때 파라미터가 많아지면 물음표(?)의 경우 순서를 파악하기 어려워 진다. $pdoStatement = $pdo -> prepare("SELECT * FROM member WHERE name = ? OR email = ?"); $pdoStatement = bindValue(1, $name); $pdoStatement = bindValue(2, $email); ?> |
#03. 쿼리 파라미터에 값 배정(데이터 바인딩)
쿼리 파라미터에 값을 대입할 때는 PDOStatement:bindValue, PDOStatement::bindParam 두 가지 메소드를 이용한다.
binValue 메소드는 값을 집접 대입하고, bindParam은 변수를 대입한다.
# 소스코드 - bindValue를 사용한 경우
<?php $name = "Kei"; $stmt = $pdo -> prepare("SELECT * FROM girl_group WHERE name = :name"); $stmt -> bindValue(":name", $name); // 변수에 바인딩 하기 위해 bindValue을 사용 $name = "서지수"; // 쿼리를 실행하기전에 $name 변수의 값을 변경한다. $stmt -> execute(); // WHERE name = "Kei"으로 실행된다. $row = $stmt -> fetch(); // 객체가 실행한 쿼리의 결과값 가져오기 echo "<pre>"; print_r($row); echo "</pre>"; ?> |
# 출력결과 - bindValue를 사용한 경우
※ binvValue는 값 작체를 대입한다. 그래서 위와같이 변수에 새로운 값을 넣어도 반영되지 않는다.
# 소스코드 - bindParam를 사용한 경우
<?php $name = "유지애"; $stmt = $pdo -> prepare("SELECT * FROM girl_group WHERE name = :name"); $stmt -> bindParam(":name", $name); // 변수에 바인딩 하기 위해 bindParam을 사용 $name = "정예인"; // 쿼리를 실행하기전에 $name 변수의 값을 변경한다. $stmt -> execute(); // WHERE name = "정예인"으로 실행된다. $row = $stmt -> fetch(); // 객체가 실행한 쿼리의 결과값 가져오기 echo "<pre>"; print_r($row); echo "</pre>"; ?> |
# 출력결과 - bindParam를 사용한 경우
※ bindParam은 변수를 대입한것이기 때문에 대입한 이후에 값을 변경할 수 있다.
#04. 쿼리 실행
PDOStament 객체에 준비된 쿼리를 실행할 때는 PDOStatement::execute() 메소드를 사용한다.
<?php $pdoStatement = $pdo -> prepare("SELECT * FROM member WHERE name = :name OR email = :email"); $pdoStatement -> bindValue(":name", $name); $pdoStatement -> bindValue(":email", $email); $pdoStatement -> execute(); ?> |
데이터 바인딩을 하지 않고, PDOStatment::execute() 메소드의 인수로 쿼리 파라미터에 사용할 값을 넘겨줄 수 있다.
<?php $pdoStatement = $pdo -> prepare("SELECT * FROM member WHERE name = ? OR email = ?"); $pdoStatement -> execute([ ":name" => $name , ":email" => $email ]); ?> |
이렇게 쓸 경우에는 모든 데이터의 타입이 PDO::PARAM_STR로 처리된다.
데이터 타입을 지정하고 싶다면 PDOStatement::bindValue()나 PODStatement::bindParam()을 이용하면된다.
PDOStatement::execute() 메소드는 쿼리 실행이 성공했는지만 되돌려준다.
쿼리 실행 결과를 되돌려주지않는다는 점에 주의해야 한다.
추출한 데이터나 쿼리에 영향받은 행의 개수는 별도의 메소드를 이용해 조회해야 한다.
#05. 결과값 조회 - 데이터 가져오기
① PDOStatement:fetch()
쿼리 결과를 가져올 때는 PDOStatement:fetch() 메소드를 사용한다.
PDOStatement::fetch() 메소드를 한번 실행하면 쿼리 결과에서 한 행을 가져온다.
그래서 다음과 같이 반복문으로 모든 행을 가져와서 처리하는 경우가 많다.
# 소스코드
<? $dbHost = "localhost"; $dbName = "test_db"; $dbUser = "tester"; $dbPass = "1q2w3e"; $dbChar = "utf8"; $dsn = "mysql:host={$dbHost};dbname={$dbName};charset={$dbChar}"; $pdo = new PDO($dsn, $dbUser, $dbPass); $group = "REDVELVET"; $stmt = $pdo -> prepare("SELECT * FROM girl_group WHERE group_name = :group"); $stmt -> bindValue(":group", $group); $stmt -> execute(); $result = array(); while($row = $stmt -> fetch()) { $result[] = $row; } echo "<pre>"; print_r($result); echo "</pre>"; ?> |
# 출력결과
② PDOStatement:fetchAll()
한 번에 모든 행을 가져오고자 할 때는 PDOStatement::fetchAll() 메소드를 사용한다.
<? $dbHost = "localhost"; $dbName = "test_db"; $dbUser = "tester"; $dbPass = "1q2w3e"; $dbChar = "utf8"; $dsn = "mysql:host={$dbHost};dbname={$dbName};charset={$dbChar}"; $pdo = new PDO($dsn, $dbUser, $dbPass); $group = "REDVELVET"; $stmt = $pdo -> prepare("SELECT * FROM girl_group WHERE group_name = :group"); $stmt -> bindValue(":group", $group); $stmt -> execute(); $result = $stmt -> fetchAll(); echo "<pre>"; print_r($result); echo "</pre>"; ?> |
PDOStatement::fetchAll()을 사용할 때 데어티베이스에서 조회한 값이 많으면 메모리 부족으로 웹 서버가 다운될 수 있다.
조회 결과가 적다는 확신이 있는 때에만 신중하게 사용해야 한다.
#06. 결과값 조회 - 모드 지정
결과를 가져오는 모드는 PDO::FETCH_로 시작하는 혜약 상수를 PDOStatement::fetch(), PDOStatment::fetchAll() 메소드의 인수로 넘겨주어 지정한다.
자주 사용하는 예약 상수는 아래와 같다.
① PDO::FETCH_BOTH
-. PDOStatement::fetch(), PODStatement::fetchAll() 메소드에 가져오기 모드를 지정해주지 않으면 PDO:FETCH_BOTH 모드로 결과를 가져온다.
-. PDO::FETCH_BOTH는 결과값을 가져올 때 데이터베이스의 칼럼 이름을 키로 사용하는 배열과 칼럼의 순서를 키로 사용하는 배열, 둘 다 만드는 방식이다.
-. 두가지 배열을 만들기 때문에 당연히 성능은 좋지 않다.
-. 될 수 있는대로 사용하고자 하는 모드를 지정해서 사용하는것이 좋다.
② PDO::FETCH_ASSOC
-. 컬럼명을 키로 사용하는 연관 배열을 반환한다.
-. 가져온 데이터는 $row['id']와 같은 식드로 사용한다.
# 소스코드
<?php $dbHost = "localhost"; $dbName = "test_db"; $dbUser = "tester"; $dbPass = "1q2w3e"; $dbChar = "utf8"; $dsn = "mysql:host={$dbHost};dbname={$dbName};charset={$dbChar}"; $pdo = new PDO($dsn, $dbUser, $dbPass); $stmt = $pdo -> prepare("SELECT * FROM girl_group WHERE name = :name"); $stmt -> bindValue(":name", "사나"); $stmt -> execute(); $row = $stmt -> fetch(PDO::FETCH_ASSOC); echo "그룹 : ".$row['group_name']; echo "<br/>"; echo "이름 : ".$row['name']; echo "<br/>"; echo "포지션 : ".$row['position']; echo "<br/>"; echo "회사 : ".$row['company']; ?> |
# 출력결과
③ PDO::FETCH_NUM
-. 컬럼의 순서를 키로 사용하는 배여을 반환한다.
-. 가져온 데이터는 $row[0] 과 같은 식으로 사용한다.
-. 객체로 반환한다.
-. 반환된 객체는 데이터베이스 컬럼명에 해당하는 프로퍼티를 갖고 있다.
-. 가져온 데이터는 $row - > id와 같이 사용할 수 있다.
⑤ PDO::FETCH_CLASS
-. 지정한 클래스의 객체로 반환한다.
-. PDO::FETCH_OBJ와 마찬가지로 컬럼명에 해당하는 프로퍼티에 값을 가진다.
# 소스코드 - 클래스
<?php class iDol { private $idx; private $name; private $position; private $group; private $company; private $birthDay; public function getIdx() { return $this -> idx; } public function getName() { return $this -> name; } public function getPosition() { return $this -> position; } public function getGroup() { return $this -> group_name; } public function getCompany() { return $this -> company; } public function getBirthDay() { return $this -> birthDay; } } ?> |
# 소스코드 - PDO::FETCH_CLASS를 사용한 출력
<?php $dbHost = "localhost"; $dbName = "test_db"; $dbUser = "tester"; $dbPass = "1q2w3e"; $dbChar = "utf8"; $dsn = "mysql:host={$dbHost};dbname={$dbName};charset={$dbChar}"; $pdo = new PDO($dsn, $dbUser, $dbPass); $stmt = $pdo -> prepare("SELECT * FROM girl_group WHERE name = :name"); $stmt -> bindValue(":name", "다현"); $stmt -> execute(); $stmt -> setFetchMode(PDO::FETCH_CLASS, "iDol"); $row = $stmt -> fetch(); echo $row -> getGroup(); echo "<br/>"; echo $row -> getName(); echo "<br/>"; echo $row -> getPosition(); echo "<br/>"; echo $row -> getCompany(); ?> |
# 출력결과
⑥ PDOStatement::fetchColumn()
-. PDOStatement::fetchColumn()은 결과값 중 하나의 컬럼값만 가져오는 메소드 이다.
-. 데이터 수를 조회할 경우 처럼 단 하나의 컬럼값만 필요한 경우 유용하게 사용할 수 있다.
# 소스코드
<?php $dbHost = "localhost"; $dbName = "test_db"; $dbUser = "tester"; $dbPass = "1q2w3e"; $dbChar = "utf8"; $dsn = "mysql:host={$dbHost};dbname={$dbName};charset={$dbChar}"; $pdo = new PDO($dsn, $dbUser, $dbPass); $stmt = $pdo -> prepare("SELECT COUNT(*) AS count FROM girl_group WHERE group_name = :group"); $stmt -> bindValue(":group", "TWICE"); $stmt -> execute(); $row = $stmt -> fetchColumn(); echo "트와이스 인원 : ".$row; ?> |
# 출력결과
⑦ PDO:lastInsertId()
-. 데이터베이스에 새로운 데이터를 입력하고 id 혹은 index값을 바로 사용해야 하는경우가 있다.
-. PDO::lastInsertId()를 객체를 이용하면 마지막으로 입력한 데이터의 id 혹은 index 값을 확인 할 수 있다.
※ PDOStatement가 아닌 PDO 객체를 사용하는것에 주의
# 소스코드
<?php $dbHost = "localhost"; $dbName = "test_db"; $dbUser = "tester"; $dbPass = "1q2w3e"; $dbChar = "utf8"; $dsn = "mysql:host={$dbHost};dbname={$dbName};charset={$dbChar}"; $pdo = new PDO($dsn, $dbUser, $dbPass); $stmt = $pdo -> prepare(" INSERT INTO girl_group (name, position, group_name, company, birthday) VALUE (:name, :position, :group, :company, :birthday) "); $stmt -> bindValue(":name", "정채연"); $stmt -> bindValue(":position", "센터, 서브보컬"); $stmt -> bindValue(":group", "DIA"); $stmt -> bindValue(":company", "MBK엔터테인먼트"); $stmt -> bindValue(":birthday", "1997-12-01 00:00:00"); $stmt -> execute(); $taskIdx = $pdo -> lastInsertId(); echo "정채연 등록번호 : ".$taskIdx; ?> |
# 출력결과
출처 : https://wickedmagica.tistory.com/16