728x90

로그인 6개월 차단하는 기능을 구현해 달라는 요청으로 간단 구현한 사항 적어둔다.

 

<?php
error_reporting(0);
/*
ini_set("display_startup_errors", 1);
ini_set("display_errors", 1);
error_reporting(E_ALL);
// */
 
require_once 'path.php';// root 폴더를 기준으로 상대적인 경로 자동 구하기
require_once $g['path_root'].'sessionChk.php'// 세션 체크
require_once $g['path_root'].'ipFiltering.php';
require_once $g['path_class'].'dbconnect.php';
$a = new DBDataClass();
 
date_default_timezone_set('Asia/Seoul');
$today = date("Y-m-d"); // 오늘 날짜
 
// getDbUpdate($table, $set, $params, $where);
// $access // 이용권한 없음(0), 로그인 허용(1), 가입승인대기(2)
$rows = $a->getDbresult('members','admin=0','date, idx, regdate');
foreach($rows as $R):
    if($R['date'!== NULL) {
        // 최근접속일자
        $from_day = substr($R['date'],0,4)."-".substr($R['date'],4,2)."-".substr($R['date'],6,2);
        echo $from_day.'<br />';
        $from = new DateTime($from_day);
        $to = new DateTime($today);
        $day_diff = $from->diff($to)->days;
        if($day_diff > 180){ // 6개월동안 로그인한 기록이 없으면
            $SET ="access=0"// 접속을 차단시킨다.
            $params=array($R['idx']);
            $a->getDbUpdate('members',$SET,$params,'idx=?');
            echo 'idx : '.$R[1].'<br />';
        }
    } else {
        // 가입일자
        $from_day = substr($R['regdate'],0,4)."-".substr($R['regdate'],4,2)."-".substr($R['regdate'],6,2);
        echo $from_day.'<br />';
        $from = new DateTime($from_day);
        $to = new DateTime($today);
        $day_diff = $from->diff($to)->days;
        if($day_diff > 365){
            $SET ="access=2";
            $params=array($R['idx']);
            $a->getDbUpdate('members',$SET,$params,'idx=?');
            echo 'no access : '.$R[1].'<br />';
        }
    }
endforeach;
?>
 

 

 

<?php
class DBDataClass {
    protected $db// 변수를 선언한 클래스와 상속받은 클래스에서 참조할 수 있다.
 
    public function __construct() {
        $this->dbConnect();
        // construct 메소드는 객체가 생성(인스턴스화)될 때 자동으로 실행되는 특수한 메소드다.
    }
 
    private function dbConnect() {
        require_once 'dbinfo.php';
        try {
            // MySQL PDO 객체 생성
            $this->db = new PDO(_DSN, _DBUSER, _DBPASS);
            $this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
            $this->db->setAttribute(PDO::ATTR_EMULATE_PREPARES, FALSE);
        } catch(PDOException $ex) {
            die("오류 : " . $ex->getMessage());
        }
    }
 
    /*
     $sql = "INSERT INTO users (name, surname, sex) VALUES (?,?,?)";
     $stmt= $pdo->prepare($sql);
     $stmt->execute(array($name, $surname, $sex));
    */
    public function recur_quot($cnt) {
        $R = array();
        for ($i = 0$i < $cnt$i++) {
            array_push($R"?");
        }
        return implode(","$R); // 배열을 문자열로
    }
 
    // 신규 자료 추가(ok)
    function putDbInsert($table$key$params) {
        try {
            $this->db->beginTransaction();
            $sql = "insert into " . $table . " (" . $key . ") values(" . $this->recur_quot(count($params)) . ")";
            $stmt = $this->db->prepare($sql);
            $status = $stmt->execute($params); // $params 는 배열 값
            $this->db->commit();
            return 1;
        } catch (PDOException $pex) {
            $this->db->rollBack();
            echo "에러 : " . $pex->getMessage();
            return 0;
        }
    }
 
 
    function getDbUpdate($table$set$params$where) {
        $sql = "update " . $table . " set " . $set . ($where ? ' where ' . $where : '');
        try {
            $this->db->beginTransaction();
            $stmt = $this->db->prepare($sql);
            $status = $stmt->execute($params);
            $this->db->commit();
            return 1;
        } catch (PDOException $pex) {
            $this->db->rollBack();
            echo "에러 : " . $pex->getMessage();
            return 0;
        }
    }
 
    public function putDbArray($sql$params) {
        // $params : array 를 사용해야 한다.
        $stmt = $this->db->prepare($sql);
        $stmt->execute($params);
        return $stmt->fetchAll(); //foreach 문과 연동하여 결과처리
    }
 
    
    // 검색조건에 일치하는 데이터 가져오기
    public function getDbData($table$where$column$returntype = '') {
        $sql = 'select ' . $column . ' from ' . $table . ($where ? ' where ' . $this->getSqlFilter($where) : '');
        $stmt = $this->db->prepare($sql);
        $stmt->execute();
        if ($returntype == 1) {
            return $stmt->fetch(PDO::FETCH_ASSOC);
        } else {
            return $stmt->fetch();
        }
    }
 
    // DB Query result 함수
    function getDbresult($table,$where,$column) {
        $sql = 'select ' . $column . ' from ' . $table . ($where ? ' where ' . $this->getSqlFilter($where) : '');
        //echo $sql.'<br />';
        $stmt = $this->db->prepare($sql);
        $stmt->execute();
        return $stmt->fetchAll();
    }
 
    // table 결과 조회 용도
    public function getDbArray($table$where$column$orderby$rowsPage$curPage) {
        $sql = 'select ' . $column . ' from ' . $table . ($where ? ' where ' . $this->getSqlFilter($where) : '') . ($orderby ? ' order by ' . $orderby : '') . ($rowsPage ? ' limit ' . (($curPage - 1* $rowsPage) . ', ' . $rowsPage : '');
        $stmt = $this->db->prepare($sql);
        $stmt->execute();
        return $stmt->fetchAll(); //foreach 문과 연동하여 결과처리 하면 됨
    }
 
    //SQL필터링
    public function getSqlFilter($sql) {
        //$sql = preg_replace("/[\;]+/","", $sql); // 공백은 제거 불가
        return $sql;
    }
 
?>
 

 

 

 

 

블로그 이미지

Link2Me

,
728x90

PDO에서 NULL 값을 반환하는 것이 Legacy PHP 와 약간 달라서 주의가 필요해서 적어둔다.

 

<?php
class adminClass extends DBDataClass {
 
    function BldTypeNMFromID($codeID){
        $sql = "select codeNM from bldingTypeNM where codeID=?";
        $params = array($codeID);
        $stmt = $this->db->prepare($sql);
        $stmt->execute($params);
        if($row = $stmt->fetch()){
            return $row[0];
        }
    }
 
    function BldTypeIDFromNM($codeNM){
        if(strlen($codeNM< 1return 2// 값이 없으면, 2번 코드 반환
        $sql = "select codeID from bldingTypeNM where codeNM=?";
        $params = array($codeNM);
        $stmt = $this->db->prepare($sql);
        $stmt->execute($params);
        if($row = $stmt->fetch()){
            return $row[0];
        } else { // NULL 인 경우, 신규 추가 및 신규 추가한 코드 반환
            $this->putBldTypeIDFromNM($codeNM);
            return $this->maxBldTypeID();
        }
    }
 
    function putBldTypeIDFromNM($codeNM) {
        if($this->isBldTypeID($codeNM== 0){
            $sort = $this->maxBldTypeID();
            $sort = (int)$sort + 1;
            $sql = "INSERT INTO bldingTypeNM (codeID, codeNM, sort) VALUES (?,?,?)";
            $params = array($sort,$codeNM,$sort);
            $stmt = $this->db->prepare($sql);
            $stmt->execute($params);
        } 
 
    }
 
    function maxBldTypeID(){
        $sql = "select max(codeID) FROM bldingTypeNM";
        $stmt = $this->db->prepare($sql);
        $stmt->execute();
        if($row = $stmt->fetch()){
            return $row[0];
        }
    }
 
    function isBldTypeID($codeNM){ 
        $sql = "select count(*) FROM bldingTypeNM WHERE codeNM=?";
        $params = array($codeNM);
        $stmt = $this->db->prepare($sql);
        $stmt->execute($params);
        if($row = $stmt->fetch()){
            return $row[0];
        } else {
            return 0;
        }
    }
 
    function BldTypeMNArray(){
        $sql="SELECT * from bldingTypeNM order by sort ASC";
        $stmt = $this->db->prepare($sql);
        $stmt->execute();
        while($row = $stmt->fetch()){
            $charge[$row[0]]=$row[1];
        }
        return $charge;
    }
}
 
?>

 

테이블 구조

CREATE TABLE `bldingTypeNM` (
  `codeID` int(10UNSIGNED NOT NULL DEFAULT 0,
  `codeNM` varchar(40DEFAULT NULL,
  `sort` int(3NOT NULL DEFAULT 1
ENGINE=MyISAM DEFAULT CHARSET=utf8;
 
ALTER TABLE `bldingTypeNM`
  ADD PRIMARY KEY (`codeID`);
 

 

블로그 이미지

Link2Me

,
728x90

통상적으로 쉽고 간단하게 하기 위해 positional placeholders 를 이용한 방법을 사용한다.

$sql = "UPDATE users SET name=?, surname=?, sex=? WHERE id=?";
$params = array($name, $surname, $sex, $id);
$stmt= $pdo->prepare($sql);
$stmt->execute($params);

 

UPDATE query with named placeholders

$data = array('name' => $name,'surname' => $surname,'sex' => $sex,'id' => $id);
$sql = "UPDATE users SET name=:name, surname=:surname, sex=:sex WHERE id=:id";
$stmt= $pdo->prepare($sql);
$stmt->execute($data);

 

 

비밀번호 변경하는 간단 예제이다.

<?php
$idx = 15;
$passwd = "new1234!"// 비밀번호
 
require_once 'path.php';// root 폴더를 기준으로 상대적인 경로 자동 구하기
require_once $g['path_config'].'config.php';
require_once $g['path_class'].'dbconnect.php';
require_once $g['path_class'].'loginClass.php';
$d = new LoginClass;
 
$hash = $d->hashSSHA($passwd);
$encrypted_pw = $hash['encrypted']; // encrypted password
$salt = $hash['salt']; // salt
 
$QKEY = "passwd=?,salt=?";
$params = array($encrypted_pw,$salt,$idx);
$d->getDbUpdate('members',$QKEY,$params,'idx=?');
 
echo '변경완료'
 
?>
 
<?php
class LoginClass {
    // 패스워드 해쉬함수 : 20자 SALT + SHA256 암호화
    function hashSSHA($password) {
        $salt = sha1(rand());
        $salt = substr($salt020);
        $encrypted = base64_encode(hash('sha256'$salt.$passwordtrue) . $salt);
        $hash = array("salt" => $salt"encrypted" => $encrypted);
        return $hash;
    }
 
    function getDbUpdate($table$set$params$where) {
        $sql = "update " . $table . " set " . $set . ($where ? ' where ' . $where : '');
        try {
            $this->db->beginTransaction();
            $stmt = $this->db->prepare($sql);
            $status = $stmt->execute($params);
            $this->db->commit();
            return 1;
        } catch (PDOException $pex) {
            $this->db->rollBack();
            echo "에러 : " . $pex->getMessage();
            return -1
        }
    }
}
?>
 

 

 

기본적인 Insert, Update, Delete, 검색에 대한 샘플이다.

 
<?php
    require_once('dbCon.php');
    $pdo = dbConnect();
    
    //입력처리
    if(isset($_POST['action']) && $_POST['action'== 'insert'){
        try{
            $pdo->beginTransaction();
            $sql = "INSERT INTO member (name, age, email) VALUES(:name, :age, :email)";
            $stmt = $pdo->prepare($sql);
            $stmt->bindValue(':name',$_POST['name'],PDO::PARAM_STR);
            $stmt->bindValue(':age',$_POST['age'],PDO::PARAM_INT);
            $stmt->bindValue(':email'$_POST['email'], PDO::PARAM_STR);
            $stmt->execute();
            $pdo->commit();
            print "데이터 입력 처리 하였습니다.<br/><br/>";
        } catch (PDOException $pex) {
            $pdo->rollBack();
            print "에러 : ".$pex->getMessage();
        }
    }
    
    //수정처리
    if(isset($_POST['action']) && $_POST['action']=='update'){
        //세션 변수를 이용하여 id값을 획득한다.
        $id = $_SESSION['id'];
        try{
            $pdo->beginTransaction();
            $sql = "UPDATE member SET name = :name, age=:age, email = :email WHERE id = :id";
            $stmt = $pdo->prepare($sql);
            $stmt->bindValue(':name'$_POST['name'], PDO::PARAM_STR);
            $stmt->bindValue(':age'$_POST['age'],PDO::PARAM_INT);
            $stmt->bindValue(':email'$_POST['email'], PDO::PARAM_STR);
            $stmt->bindValue(':id',$id,PDO::PARAM_INT);
            
            $stmt->execute();
            $pdo->commit();
            print "데이터를 수정 처리하였습니다.<br/>";
        } catch (PDOException $pex) {
            $pdo->rollBack();
            print "에러 : ".$pex->getMessage();
        }
        // 사용한 세션 변수를 삭제한다.
        unset($_SESSION['id']);
    }
    
    //삭제 처리
    if(isset($_GET['action']) && $_GET['action']=='delete' && $_GET['id'> 0){
        try{
            $pdo->beginTransaction();
            $id = $_GET['id'];
            $sql = "DELETE FROM member WHERE id = :id";
            $stmt = $pdo->prepare($sql);
            $stmt->bindValue(':id'$id, PDO::PARAM_INT);
            $stmt->execute();
            $pdo->commit();
            print "데이터를 삭제 처리하였습니다...<br/><br/>";
        } catch (PDOException $pex) {
            $pdo->rollBack();
            print "에러 : ".$pex->getMessage();
        }
    }
    
    //검색 처리
    try{
        if(isset($_POST['searchWord']) && $_POST['searchWord'!= ""){
            $searchWord = '%'.$_POST['searchWord'].'%';
            $sql = "SELECT * FROM member WHERE name LIKE :name";
            $stmt=$pdo->prepare($sql);
            $stmt->bindValue(':name',$searchWord,PDO::PARAM_STR);
 
            $stmt->execute();
        }else{
            //현재 DB에 있는 회원리스트를 모두 출력한다.
            $sql = "SELECT * FROM member";
            $stmt = $pdo->query($sql);
        }
        
        $cnt = $stmt->rowCount();
    }catch(PDOException $pex){
        print "에러 : ".$pex->getMessage();
    }    
    if($cnt < 1){
        print "검색 결과가 없습니다!!<br/>";
    }
?>

 

블로그 이미지

Link2Me

,
728x90

오랫만에 PHP PDO 코드를 만들다보니 에러가 나와 어디를 수정해야 할지 헷갈린다.

PHP PDO로 구현한 코드에서 500 Error 메시지의 원인을 찾아내고 해결한 후 적어둔다.

 

Ajax 는 PHP B 파일에서 발생하는 에러를 파악하기 어려울 수 있다.

이럴경우에는 절분시험을 하듯이 B 파일에서만 별도로 데이터가 넘어왔다는 가정하에

아래 두줄 추가하고 alert(msg) 해서 메시지 내용을 살펴보고 코드의 문제점을 발견하고 찾아서 해결하면 된다.

<?php
error_reporting(E_ALL);
ini_set("display_errors", 1);

 

if (!isset($_POST['uid'])) {
    echo "-9";
    exit ;
}

 

에러를 무조건 출력시켜서 메시지를 분석해야 잘못된 부분을 찾아내기 쉽다.

 

 

 

 

 

 

 

코딩할 당시에는 알지만 오래되면 기억이 나지 않기 때문에 내가 나중에 보려고 정리하는 것이므로 댓글 태클은 정중히 사양.......

블로그 이미지

Link2Me

,
728x90

PHP 절차지형 방식으로 MySQL 을 연동한 코드를 PDO 방식으로 변환할 때 필요한 정보를 적어둔다.

PDO 는 CUBRID, MS SQL Server, Firebird/Interbase, IBM, Informix, MySQL, Oracle, ODBC and DB2, PostgreSQL, SQLite, 4D 등 12가지 DB를 지원한다.

 

PHP 절차지향
PDO
 mysql_fetch_assoc($result)  $stmt->fetch(PDO::FETCH_ASSOC)
 mysql_num_rows($result)  $stmt->rowCount()
 while ($row = mysql_fetch_assoc($result)) {}  foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {}

 

SELECT 변환 예제

 $sql="select count(*), access from members where userID='".$userID."'";
 $result=mysqli_query($db,$sql);
 if($row=mysqli_fetch_row($result)){
 $sql="select count(*), access from members where userID=?";
 $params = array($userID);
 $stmt = $this->db->prepare($sql);
 $stmt->execute($params);
 if($row = $stmt->fetch()){

 

Update 변환 예제

 $sql = "UPDATE rb_accessLog SET hit=hit+1 Where ipaddr='".$access_ip."' and LogID='".$userID."'";
 $result=mysqli_query($db,$sql);
 $sql = "UPDATE rb_accessLog SET hit=hit+1 Where ipaddr=? and LogID=?";
 try {
    $this->db->beginTransaction();
    $params = array($access_ip,$userID);
    $stmt = $this->db->prepare($sql);
    $stmt->execute($params);
    $this->db->commit();
 } catch (PDOException $pex) {
    $this->db->rollBack();
    echo "에러 : " . $pex -> getMessage();
 }

 

Insert 변환 예제

 $sql ="INSERT INTO bbs_imgpath (uid,relateduid,imgpath) values(NULL,";
 $sql.="'".$uid."', '".$v."')";
 mysqli_query($this->db,$sql);
 $key = "uid,relateduid,imgpath";
 $val = "NULL,$uid,$v";
 $this->getDbInsert('bbs_imgpath', $key, $val);

 

getDbInsert 함수는 아래와 같이 만들었다.

<?php
 function getDbInsert($table$key$val) {
        try {
            $params = explode(','$val); // 문자열을 분리하여 배열로 만듬
            $this->db->beginTransaction();
            $sql = "insert into " . $table . " (" . $key . ") values(" . $this->recur_quot(count($params)) . ")";
            $stmt = $this->db->prepare($sql);
            $status = $stmt->execute($params); // $params 는 배열 값
            if ($status) {
                $this->db->commit();
                return 1;
            } else {
                return 0;
            }
        } catch (PDOException $pex) {
            $this->db->rollBack();
            echo "에러 : " . $pex->getMessage();
        }
    }
 
    public function recur_quot($cnt) {
        $R = array();
        for ($i = 0$i < $cnt$i++) {
            array_push($R"?");
        }
        return implode(","$R); // 배열을 문자열로
    } 
 
?
 

 

Delete 변환 예제

 $sql = "DELETE FROM bbs_imgpath where imgpath='".$v."' and relateduid=".$uid;
 mysqli_query($this->db,$sql);
 
 $where = "imgpath=? and relateduid=?";
 $params = array($v,$uid);
 $this->getDbDelete('bbs_imgpath', $where, $params);

 

getDbDelete 함수는 아래와 같이 만들었다.

 
 
<?php
    public function getDbDelete($table$where$params) {
        if ($this->isDataExisted($table$where$params)) {
            try {
                $this->db->beginTransaction();
                $sql = "delete from " . $table . ($where ? ' where ' . $where : '');
                $stmt = $this->db->prepare($sql);
                $status = $stmt->execute($params);
                if ($status) {// action 실행여부에 대한 결과이지 실제 데이터 삭제와는 무관하네.
                    $this->db->commit();
                    return 1// 삭제 성공
                } else {
                    return 0// 삭제 실패
                }
            } catch (PDOException $pex) {
                $this->db->rollBack();
                echo "에러 : " . $pex->getMessage();
            }
        } else {
            return 0// 삭제할 데이터가 없음
        }
 
    }
 
    // 삭제할 데이터의 존재 유무 파악
    public function isDataExisted($table$where$params) {
        $sql = 'select * from ' . $table . ($where ? ' where ' . $where : '');
        $stmt = $this->db->prepare($sql);
        $stmt->execute($params);
        if ($row = $stmt->fetch()) {
            return $row;
        } else {
            return false;
        }
    }
 
?>
 

 

사용자 함수

편리하게 사용하기 위해 만든 함수는 아래와 같이 변경하면 된다.

다만, 검색조건을 입력시 SQL Injection 필터링 함수를 한번 거쳐서 where 조건문에서 해킹 방지를 해주는 것은 필수요소다.

    function getDbData($table,$where,$column) {
        global $db;
        $sql ='select '.$column.' from '.$table.($where?' where '. $where:'');
        //echo $sql;
        $result = mysqli_query($db,$sql);
        $row = mysqli_fetch_array($result);
        return $row;
    }
     // 검색조건에 일치하는 데이터 가져오기
    public function getDbData($table, $where, $column, $returntype = '') {
        $sql = 'select ' . $column . ' from ' . $table . ($where ? ' where ' . $where : '');
        $stmt = $this->db->prepare($sql);
        $stmt->execute();
        if ($returntype == 1) {
            return $stmt->fetch(PDO::FETCH_ASSOC);
        } else {
            return $stmt->fetch();
        }
    }
     // 검색조건에 일치하는 데이터 가져오기
    public function getDbDataAll($table, $where, $column) {
        $sql = 'select ' . $column . ' from ' . $table . ($where ? ' where ' . $this->getSqlFilter($where) : '');
        $stmt = $this->db->prepare($sql);
        $stmt->execute();
        return $stmt->fetchAll();
    }

 

 

관련으로 만든 Class 대부분은 아래와 같다.

 
<?php
class DBDataClass {
    protected $db// 변수를 선언한 클래스와 상속받은 클래스에서 참조할 수 있다.
 
    public function __construct() {
        $this->dbConnect();
        // construct 메소드는 객체가 생성(인스턴스화)될 때 자동으로 실행되는 특수한 메소드다.
    }
 
    private function dbConnect() {
        require_once 'dbinfo.php';
        try {
            // SQL PDO 객체 생성
            $this->db = new PDO(_DSN, _DBUSER, _DBPASS);
            $this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
            $this->db->setAttribute(PDO::ATTR_EMULATE_PREPARES, FALSE);
        } catch(PDOException $ex) {
            die("오류 : " . $ex->getMessage());
        }
    }
 
    // 신규 자료 추가(ok)
    function getDbInsert($table$key$val) {
        try {
            $params = explode(','$val); // 문자열을 분리하여 배열로 만듬
            $this->db->beginTransaction();
            $sql = "insert into " . $table . " (" . $key . ") values(" . $this->recur_quot(count($params)) . ")";
            $stmt = $this->db->prepare($sql);
            $status = $stmt->execute($params); // $params 는 배열 값
            $this->db->commit();
            return 1;
        } catch (PDOException $pex) {
            $this->db->rollBack();
            echo "에러 : " . $pex->getMessage();
            return 0;
        }
    }
 
    public function recur_quot($cnt) {
        $R = array();
        for ($i = 0$i < $cnt$i++) {
            array_push($R"?");
        }
        return implode(","$R); // 배열을 문자열로
    }
 
    function getDbUpdate($table$set$params$where) {
        $sql = "update " . $table . " set " . $set . ($where ? ' where ' . $where : '');
        try {
            $this->db->beginTransaction();
            $stmt = $this->db->prepare($sql);
            $status = $stmt->execute($params);
            $this->db->commit();
            return 1;
        } catch (PDOException $pex) {
            $this->db->rollBack();
            echo "에러 : " . $pex->getMessage();
            return 0;
        }
    }
 
    public function getDbDelete($table$where$params) {
        if ($this->isDataExisted($table$where$params)) {
            try {
                $this->db->beginTransaction();
                $sql = "delete from " . $table . ($where ? ' where ' . $where : '');
                $stmt = $this->db->prepare($sql);
                $status = $stmt->execute($params);
 
                $this->db->commit();
 
                return 1// 삭제 성공
            } catch (PDOException $pex) {
                $this->db->rollBack();
                echo "에러 : " . $pex->getMessage();
            }
        } else {
            return 0// 삭제할 데이터가 없음
        }
 
    }
 
    // 삭제할 데이터의 존재 유무 파악
    public function isDataExisted($table$where$params) {
        $sql = 'select * from ' . $table . ($where ? ' where ' . $where : '');
        $stmt = $this->db->prepare($sql);
        $stmt->execute($params);
        if ($row = $stmt->fetch()) {
            return $row;
        } else {
            return false;
        }
    }
 
 
    // 검색조건에 일치하는 데이터 가져오기
    public function getDbData($table$where$column$returntype = '') {
        $sql = 'select ' . $column . ' from ' . $table . ($where ? ' where ' . $where : '');
        $stmt = $this->db->prepare($sql);
        $stmt->execute();
        if ($returntype == 1) {
            return $stmt->fetch(PDO::FETCH_ASSOC);
        } else {
            return $stmt->fetch();
        }
    }
 
    // 검색조건에 일치하는 데이터 가져오기
    public function getDbDataAll($table$where$column) {
        $sql = 'select ' . $column . ' from ' . $table . ($where ? ' where ' . $where : '');
        $stmt = $this->db->prepare($sql);
        $stmt->execute();
        return $stmt->fetchAll();
    }
 
    // DB Query result 함수
    function getDbresult($table,$where,$column) {
        $sql = 'select ' . $column . ' from ' . $table . ($where ? ' where ' .$where : '');
        $stmt = $this->db->prepare($sql);
        $stmt->execute();
        return $stmt->fetchAll();
    }
 
    // DB 레코드 총 수(ok)
    public function getDbRows($table$where) {
        $sql = 'select count(*) from ' . $table . ($where ? ' where ' . $where : '');
        $stmt = $this->db->prepare($sql);
        $stmt->execute();
        if ($row = $stmt->fetch()) {
            return $row[0];
        } else {
            return false;
        }
    }
}
?> 

 

 

 

 

블로그 이미지

Link2Me

,
728x90

Android Studio 에서 회원가입 폼을 만들고, 회원가입 처리하는 로직을 구현했다.

 

 

1. Layout 구성(activity_register.xml)

더보기

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="@color/bg_register"
    android:gravity="center"
    android:orientation="vertical"
    android:padding="10dp" >

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:orientation="vertical"
        android:paddingLeft="20dp"
        android:paddingRight="20dp">

        <EditText
            android:id="@+id/name"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="10dp"
            android:background="@color/input_register_bg"
            android:hint="@string/hint_name"
            android:inputType="textCapWords"
            android:padding="10dp"
            android:textColor="@color/input_register"
            android:textColorHint="@color/input_register_hint" />

        <EditText
            android:id="@+id/email"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="10dp"
            android:background="@color/input_register_bg"
            android:hint="@string/hint_email"
            android:inputType="textEmailAddress"
            android:padding="10dp"
            android:textColor="@color/input_register"
            android:textColorHint="@color/input_register_hint" />

        <EditText
            android:id="@+id/password"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="10dp"
            android:background="@color/input_register_bg"
            android:hint="@string/hint_password"
            android:inputType="textPassword"
            android:padding="10dp"
            android:textColor="@color/input_register"
            android:textColorHint="@color/input_register_hint" />

        <EditText
            android:id="@+id/mobileNO"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="10dp"
            android:background="@color/input_register_bg"
            android:hint="@string/hint_mobileNO"
            android:inputType="number"
            android:padding="10dp"
            android:textColor="@color/input_register"
            android:textColorHint="@color/input_register_hint" />

        <EditText
            android:id="@+id/telNO"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="10dp"
            android:background="@color/input_register_bg"
            android:hint="@string/hint_telNO"
            android:inputType="number"
            android:padding="10dp"
            android:textColor="@color/input_register"
            android:textColorHint="@color/input_register_hint" />
        <!-- Login Button -->

        <Button
            android:id="@+id/btnRegister"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dip"
            android:background="#ea4c88"
            android:text="@string/btn_register"
            android:textColor="@color/white" />

        <!-- Link to Login Screen -->

        <Button
            android:id="@+id/btnLoginScreen"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="40dip"
            android:background="@null"
            android:text="@string/btn_link_to_login"
            android:textAllCaps="false"
            android:textColor="@color/white"
            android:textSize="15dp" />
    </LinearLayout>

</LinearLayout>

 

2. Register.java

- OKHttp 라이브러리를 사용하여 코드를 작성했는데 에러가 발생해서 포기하고

  HttpUrlConnection 을 사용하여 코드를 구현했다. 100% 문제없이 동작하는 코드가 최고다.

package com.tistory.link2me.pdologin;

import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import com.tistory.link2me.common.PHPComm;

import okhttp3.MediaType;

public class Register extends AppCompatActivity {
    private static final String TAG = Register.class.getSimpleName();
    private Button btnRegister;
    private Button btnLogin;
    private EditText inputUserName;
    private EditText inputEmail;
    private EditText inputPassword;
    private EditText inputMobileNO;
    private EditText inputTelNO;
    private String userID; // SharedPreferences 저장 목적으로 Class 전역변수 선언

    public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
    public SharedPreferences settings;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_register);

        inputUserName = (EditText) findViewById(R.id.name);
        inputEmail = (EditText) findViewById(R.id.email);
        inputPassword = (EditText) findViewById(R.id.password);
        inputMobileNO = (EditText) findViewById(R.id.mobileNO);
        inputTelNO = (EditText) findViewById(R.id.telNO);
        btnRegister = (Button) findViewById(R.id.btnRegister);
        btnLogin = (Button) findViewById(R.id.btnLoginScreen);

        // 등록 버튼
        btnRegister.setOnClickListener(new View.OnClickListener() {
            public void onClick(View view) {
                String name = inputUserName.getText().toString().trim();
                String email = inputEmail.getText().toString().trim();
                userID = email;
                String password = inputPassword.getText().toString().trim();
                String mobileNO = inputMobileNO.getText().toString().trim();
                String telNO = inputTelNO.getText().toString().trim();

                if (!name.isEmpty() && !email.isEmpty() && !password.isEmpty() && !mobileNO.isEmpty()) {
                    registerUser(name, email, password, mobileNO, telNO);
                } else {
                    Toast.makeText(getApplicationContext(), "필수사항은 모두 입력하세요!", Toast.LENGTH_LONG).show();
                }
            }
        });

        // Login Screen 으로 Activity 이동
        btnLogin.setOnClickListener(new View.OnClickListener() {
            public void onClick(View view) {
                startActivity(new Intent(getApplicationContext(), Login.class));
                finish();
            }
        });

    }

    private void registerUser(String name, String userID, String password, String mobileNO, String telNO) {
        // 전달할 인자들
        Uri.Builder builder = new Uri.Builder()
                .appendQueryParameter("name", name)
                .appendQueryParameter("userID", userID)
                .appendQueryParameter("email", userID)
                .appendQueryParameter("password", password)
                .appendQueryParameter("mobileNO", mobileNO)
                .appendQueryParameter("telNO", telNO);
        String urlParameters = builder.build().getEncodedQuery();
        new getJSONData().execute(Value.IPADDRESS + "/register.php", urlParameters);
    }

    private class getJSONData extends AsyncTask<String, Void, String> {
        ProgressDialog pdLoading = new ProgressDialog(Register.this);

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            pdLoading.setMessage("\t회원가입 처리중...");
            pdLoading.setCancelable(false);
            pdLoading.show();
        }

        @Override
        protected String doInBackground(String... params) {
            try {
                return PHPComm.getJson(params[0],params[1]);
            } catch (Exception e) {
                return new String("Exception: " + e.getMessage());
            }
        }

        protected void onPostExecute(String result){
            pdLoading.dismiss();
            showJSONResult(result);
        }
    }

    protected void showJSONResult(String result) {
        if(result.equalsIgnoreCase("1")){
            Toast.makeText(this, "회원가입 완료", Toast.LENGTH_SHORT).show();
            storeUserData();
            startActivity(new Intent(getApplicationContext(), Login.class));
            finish();
        } else if(result.equalsIgnoreCase("-1")){
            Toast.makeText(this, "아이디가 이미 가입되어 있습니다", Toast.LENGTH_SHORT).show();
            inputEmail.clearFocus();
        } else {
            Toast.makeText(this, "회원 등록에 문제가 발생했습니다", Toast.LENGTH_SHORT).show();
        }
    }

    private void storeUserData(){
        settings = getSharedPreferences("settings", Activity.MODE_PRIVATE);
        SharedPreferences.Editor editor = settings.edit();
        editor.putString("userID", userID);
        editor.putBoolean("autologin", true);
        editor.commit();
    }
}

 

3. PHP 코드 작성(register.php)

<?php
extract($_POST);
if(isset($_POST) && !empty($_POST)) {
    require_once '../config/config.php';
    $c = new MemberClass();

    // 동일한 userID 등록되어 있는지 체크
    if ($c->isUserExisted($userID)) { // E-Mail 이 key value
        // user already existed
        echo -1;
    } else {
        // 사용자 등록
        $telNO = preg_replace("/[^0-9]/", "", $telNO);
        $mobileNO = preg_replace("/[^0-9]/", "", $mobileNO);
        $user = $c->storeUser($userID, $name, $email, $password, $telNO, $mobileNO);
        if ($user) { // 사용자 등록 성공
            echo 1;
        } else {
            echo 0;
        }
    }
} else { // 입력받은 데이터에 문제가 있을 경우
    echo -2;
}
?>
 

 

values.zip
다운로드

 

strings.xml 과 colors.xml 파일 두개가 포함되어 있다.

 

회원가입 테이블에 데이터가 저장되어 있는 걸 확인할 수 있다.

 

 

회원가입과 로그인처리는 완벽하게 처리되도록 구현한 소스코드를 그대로 오픈 것이니 복사해서 이용하면 된다.

AndroidManifest.xml 파일에 Register Activity 추가된 것을 추가해줘야 에러가 발생하지 않는다.소소한 에러를 잡아가면서 해보는게 좋을거 같아서 관련으로 수정된 부분은 첨부하지 않는다.

 

도움이 되셨다면 댓글 달아주세요.

블로그 이미지

Link2Me

,
728x90

Android Studio 3.0 에서 PHP PDO(PHP Data Object) 기반 함수와 연동하여 회원가입 및 로그인처리하는 예제를 구현하고 입문자를 위해서 상세한 설명과 더불어 일부 코드를 첨부한다.

 

1. Android Studio New Module 추가하는 방법

더보기

아래 그림 순서대로 진행하면 된다.

 

 

 

 

 

앱 build.gradle 을 수정할 준비를 한다.

 

 

2. 앱 build.gradle 수정사항

더보기

 apply plugin: 'com.android.application'

android {
    compileSdkVersion 23

    defaultConfig {
        applicationId "com.tistory.link2me.pdologin"
        minSdkVersion 16
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:23.4.0'
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
    compile 'com.android.support:recyclerview-v7:24.2.0' // ListView 개선 버전
    compile "com.android.support:cardview-v7:24.2.0"
    compile 'com.squareup.okhttp3:okhttp:3.9.0'
    compile 'com.squareup.picasso:picasso:2.5.2' // 이미지 라이브러리
    compile 'com.google.code.gson:gson:2.8.2'   
}

 

처음에 세팅된 값은 compileSdkVersion 26, targetSdkVersion 26 으로 되어 있는걸 23으로 낮추면 에러메시지가 몇개 나온다.

이때 수정해줘야 할 사항은

AndroidManifest.xml 파일에서 roundicon 나오는 행을 삭제한다.

그리고 res 폴더에서 아래 그림과 같은 부분만 남기도 나머지 폴더는 과감하게 삭제해버린다.

 

위와 같이 하면 코드를 작성할 준비는 완료된 것이라고 봐도 된다.

compileSdkVersion 26, targetSdkVersion 26 그대로 두고 코드를 작성해도 된다.

추가하는 라이브러리를 하나도 사용하지 않고 코드를 작성해보려고 한다.

 

컴파일을 하다보니 에러가 발생하면서 컴파일이 안된다.

Error:Execution failed for task ':pdologin:transformDexArchiveWithExternalLibsDexMergerForDebug'.
> java.lang.RuntimeException: com.android.builder.dexing.DexArchiveMergerException: Unable to merge dex

그래서 implementation 하는 라이브러리 버전이 일치되지 않아서 생기는 문제인거 같아서 수정했더니 정상적으로 컴파일이 된다.

compileSdkVersion 24, targetSdkVersion 24 로 버전 일치 시킴

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:24.2.0'
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
    implementation 'com.android.support:recyclerview-v7:24.2.0' // ListView 개선 버전
    implementation "com.android.support:cardview-v7:24.2.0"
    implementation 'com.squareup.okhttp3:okhttp:3.9.0'
    implementation 'com.squareup.picasso:picasso:2.5.2' // 이미지 라이브러리
    implementation 'com.google.code.gson:gson:2.8.2'
} 

 

 

3. 로그인 과정 설계

- 아이디, 패스워드를 입력하고 자동로그인을 체크하면 로그인이 처리되고 MainActivity 로 넘어간다.

- 자동로그인 체크를 해두면 아이디, 패스워드를 묻지 않고 자동으로 로그인되어 MainActivity 로 넘어간다.

 

<여기서 고려할 사항>

자동로그인 체크를 하면 로그인창 화면이 표시되지 않아야 한다.

즉, 앱을 실행하자마자 바로 MainActivity 로 넘어가도록 처리하려면 별도의 Activity가 하나 필요하다는 의미다.

 

 

4. Activtiy 추가과정

더보기

Intro Activity 추가

 

위 그림에서 3번은 생략하면 된다.

테스트삼아 선택을 해봤는데 필요하지 않은 기능이다.

 

Login Activity 추가

위 그림과 같은 과정으로 추가를 한다.

 

 

 

이제 AndroidManifest.xml 에서 실행순서를 로그인 처리과정 순서를 고려하여 변경해야 한다.

 <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.tistory.link2me.pdologin">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.CALL_PHONE" />
    <uses-permission android:name="android.permission.READ_CONTACTS" />
    <uses-permission android:name="android.permission.WRITE_CONTACTS" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

 

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".Intro">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".Login"></activity>
        <activity android:name=".MainActivity"></activity>
    </application>

</manifest>

 

5. Layout 구성

Layout 파일은 https://www.androidhive.info/2012/01/android-login-and-registration-with-php-mysql-and-sqlite 에서 받은 소스파일을 활용하여 필요한 사항을 수정하여 작성할 것이다.

activity_login.xml 파일 내용을 수정하고 나니 미리보기 화면이 android...actionbaroverlaylayout 라고 표시되며 보이지가 않는다.

검색해 보니 res/values/styles.xml 내용중에서 parent 부분을 아래 것으로 교체하면 정상적으로 보인다.

<style name="AppTheme" parent="Base.Theme.AppCompat.Light.DarkActionBar">
</style>

 

activity_login.xml

더보기

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/bg_login"
    android:gravity="center"
    android:orientation="vertical"
    android:padding="10dp" >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:orientation="vertical"
        android:paddingLeft="20dp"
        android:paddingRight="20dp" >

        <EditText
            android:id="@+id/login_id_edit"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="10dp"
            android:background="@color/white"
            android:hint="@string/hint_email"
            android:inputType="textEmailAddress"
            android:padding="10dp"
            android:singleLine="true"
            android:textColor="@color/input_login"
            android:textColorHint="@color/input_login_hint" />

        <EditText
            android:id="@+id/login_pw_edit"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="10dp"
            android:background="@color/white"
            android:hint="@string/hint_password"
            android:inputType="textPassword"
            android:padding="10dp"
            android:singleLine="true"
            android:textColor="@color/input_login"
            android:textColorHint="@color/input_login_hint" />

        <Button
            android:id="@+id/login_btn"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dip"
            android:background="@color/btn_login_bg"
            android:text="@string/btn_login"
            android:textColor="@color/btn_login" />

        <CheckBox
            android:id="@+id/autologin"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginTop="5dip"
            android:text="자동 로그인"
            android:textColor="#000000"
            android:textSize="14sp"
            android:textStyle="bold" />

        <!-- Link to Login Screen -->
        <Button
            android:id="@+id/btnRegisterScreen"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="30dip"
            android:background="@null"
            android:text="@string/btn_link_to_register"
            android:textAllCaps="false"
            android:textColor="@color/white"
            android:textSize="15dp" />
    </LinearLayout>

</LinearLayout>

 

6. Java 코드 작성

다른 코드는 생략하고 Login.java 코드만 적는다.

더보기

package com.tistory.link2me.pdologin;

import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.telephony.TelephonyManager;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.Toast;

import com.tistory.link2me.common.BackPressHandler;
import com.tistory.link2me.common.PHPComm;

import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.List;

public class Login extends AppCompatActivity {
    String getDeviceID; // 스마트기기의 장치 고유값
    EditText etId;
    EditText etPw;

    String userID;
    String userPW;
    CheckBox autologin;
    Boolean loginChecked;
    String idx;
    public SharedPreferences settings;
    private BackPressHandler backPressHandler;

    Context mContext;

    // 멀티 퍼미션 지정
    private String[] permissions = {
            Manifest.permission.READ_PHONE_STATE,
            Manifest.permission.CALL_PHONE, // 전화걸기 및 관리
            Manifest.permission.WRITE_CONTACTS, // 주소록 액세스 권한
            Manifest.permission.WRITE_EXTERNAL_STORAGE // 기기, 사진, 미디어, 파일 엑세스 권한
    };
    private static final int MULTIPLE_PERMISSIONS = 101;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        mContext = this.getBaseContext();
        backPressHandler = new BackPressHandler(this); // 뒤로 가기 버튼 이벤트

        if (Build.VERSION.SDK_INT >= 23) { // 안드로이드 6.0 이상일 경우 퍼미션 체크
            checkPermissions();
        }

        // 네트워크 연결상태 체크
        if(NetworkConnection() == false){
            NotConnected_showAlert();
        }

        etId = (EditText) findViewById(R.id.login_id_edit);
        etPw = (EditText) findViewById(R.id.login_pw_edit);
        autologin = (CheckBox) findViewById(R.id.autologin);

        settings = getSharedPreferences("settings", Activity.MODE_PRIVATE);
        loginChecked = settings.getBoolean("LoginChecked", false);
        if (loginChecked) {
            etId.setText(settings.getString("userID", ""));
            etPw.setText(settings.getString("userPW", ""));
            autologin.setChecked(true);
        }

        if(!settings.getString("userID", "").equals("")) etPw.requestFocus();

        Button submit = (Button) findViewById(R.id.login_btn);
        submit.setOnClickListener(new Button.OnClickListener(){
            @Override
            public void onClick(View view) {
                userID = etId.getText().toString().trim();
                userPW = etPw.getText().toString().trim();

                if(userID != null && !userID.isEmpty() && userPW != null && !userPW.isEmpty()){
                    login(userID, userPW);
                }
            }
        });

    }

    private void login(String loginID, String loginPW){
        // 단말기의 ID 정보를 얻기 위해서는 READ_PHONE_STATE 권한이 필요
        TelephonyManager mTelephony = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
        if (ActivityCompat.checkSelfPermission(mContext, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) {

        }
        if (mTelephony.getDeviceId() != null) {
            getDeviceID = mTelephony.getDeviceId();  // 스마트폰 기기 정보
        } else {
            getDeviceID = Settings.Secure.getString(getApplicationContext().getContentResolver(), Settings.Secure.ANDROID_ID);
        }

        // 전달할 인자들
        Uri.Builder builder = new Uri.Builder()
                .appendQueryParameter("userID", loginID)
                .appendQueryParameter("userPW", loginPW)
                .appendQueryParameter("deviceID", getDeviceID);
        String urlParameters = builder.build().getEncodedQuery();
        new getJSONData().execute(Value.IPADDRESS + "/loginChk.php", urlParameters);
    }

    private class getJSONData extends AsyncTask<String, Void, String> {
        ProgressDialog pdLoading = new ProgressDialog(Login.this);
        HttpURLConnection conn;

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            //this method will be running on UI thread
            pdLoading.setMessage("\tValidating user...");
            pdLoading.setCancelable(false);
            pdLoading.show();
        }

        @Override
        protected String doInBackground(String... params) {
            try {
                return PHPComm.getJson(params[0],params[1]);
            } catch (Exception e) {
                return new String("Exception: " + e.getMessage());
            }
        }

        protected void onPostExecute(String result){
            pdLoading.dismiss();
            showJSONResult(result);
        }
    }

    protected void showJSONResult(String result) {
        if(Integer.parseInt(result) > 0){ // 로그인 정보 일치하면 idx 값 받음
            idx = result;
            AutoLoginChk(); // 정보 저장
            Toast.makeText(Login.this,"로그인 성공", Toast.LENGTH_SHORT).show();
            startActivity(new Intent(getApplication(), MainActivity.class));
            finish(); // 현재 Activity 를 없애줌
        } else if(result.equalsIgnoreCase("-1")){ // 등록된 단말기와 불일치
            deviceDismatch_showAlert();
        } else if (result.equalsIgnoreCase("0")) {
            showAlert();
        } else {
            Toast.makeText(Login.this, "서버로부터 정보가 잘못 전송되었습니다", Toast.LENGTH_SHORT).show();
        }
    }

    private boolean checkPermissions() {
        int result;
        List<String> permissionList = new ArrayList<>();
        for (String pm : permissions) {
            result = ContextCompat.checkSelfPermission(this, pm);
            if (result != PackageManager.PERMISSION_GRANTED) {
                permissionList.add(pm);
            }
        }
        if (!permissionList.isEmpty()) {
            ActivityCompat.requestPermissions(this, permissionList.toArray(new String[permissionList.size()]), MULTIPLE_PERMISSIONS);
            return false;
        }
        return true;
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        switch (requestCode) {
            case MULTIPLE_PERMISSIONS: {
                if (grantResults.length > 0) {
                    for (int i = 0; i < permissions.length; i++) {
                        if (permissions[i].equals(this.permissions[i])) {
                            if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
                                showToast_PermissionDeny();
                            }
                        }
                    }
                } else {
                    showToast_PermissionDeny();
                }
                return;
            }
        }

    }

    private void showToast_PermissionDeny() {
        Toast.makeText(this, "권한 요청에 동의 해주셔야 이용 가능합니다. 설정에서 권한 허용 하시기 바랍니다.", Toast.LENGTH_SHORT).show();
        finish();
    }

    public void onStop(){
        // 어플리케이션이 화면에서 사라질때
        super.onStop();
        //AutoLoginChk();
    }

    private void AutoLoginChk(){
        // 자동 로그인이 체크되어 있고, 로그인에 성공했으면 폰에 자동로그인 정보 저장
        if (autologin.isChecked()) {
            settings = getSharedPreferences("settings",Activity.MODE_PRIVATE);
            SharedPreferences.Editor editor = settings.edit();

            editor.putString("userID", userID);
            editor.putString("userPW", userPW);
            editor.putBoolean("LoginChecked", true);
            editor.putString("idx", idx);

            editor.commit();
        } else {
            // 자동 로그인 체크가 해제되면 폰에 저장된 정보 모두 삭제
            settings = getSharedPreferences("settings",    Activity.MODE_PRIVATE);
            SharedPreferences.Editor editor = settings.edit();
            editor.clear(); // 모든 정보 삭제
            editor.commit();
        }
    }

    public void deviceDismatch_showAlert(){
        AlertDialog.Builder builder = new AlertDialog.Builder(Login.this);
        builder.setTitle("등록단말 불일치");
        builder.setMessage("최초 등록된 단말기가 아닙니다.\n" + "관리자에게 문의하여 단말기 변경신청을 하시기 바랍니다.")
                .setCancelable(false)
                .setPositiveButton("OK", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        dialog.dismiss();
                    }
                });
        AlertDialog alert = builder.create();
        alert.show();
    }

    public void showAlert(){
        AlertDialog.Builder builder = new AlertDialog.Builder(Login.this);
        builder.setTitle("로그인 에러");
        builder.setMessage("로그인 정보가 일치하지 않습니다.")
                .setCancelable(false)
                .setPositiveButton("OK", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        dialog.dismiss();
                    }
                });
        AlertDialog alert = builder.create();
        alert.show();
    }

    private void NotConnected_showAlert() {
        AlertDialog.Builder builder = new AlertDialog.Builder(Login.this);
        builder.setTitle("네트워크 연결 오류");
        builder.setMessage("사용 가능한 무선네트워크가 없습니다.\n" + "먼저 무선네트워크 연결상태를 확인해 주세요.")
                .setCancelable(false)
                .setPositiveButton("확인", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        finish(); // exit
                        //application 프로세스를 강제 종료
                        android.os.Process.killProcess(android.os.Process.myPid() );
                    }
                });
        AlertDialog alert = builder.create();
        alert.show();

    }

    private boolean NetworkConnection() {
        int[] networkTypes = ;
        try {
            ConnectivityManager manager = (ConnectivityManager) getSystemService (Context.CONNECTIVITY_SERVICE);
            for (int networkType : networkTypes) {
                NetworkInfo activeNetwork = manager.getActiveNetworkInfo();
                if(activeNetwork != null && activeNetwork.getType() == networkType){
                    return true;
                }
            }
        } catch (Exception e) {
            return false;
        }
        return false;
    }

    @Override
    public void onBackPressed() {
        backPressHandler.onBackPressed();
    }

}

 

Intro.java 코드는 Login.java 코드를 활용하면 얼마든지 만들 수 있다.

코드의 핵심로직은 다음과 같다.

// 자동 로그인 체크 검사
settings = getSharedPreferences("settings", Activity.MODE_PRIVATE);
loginChecked = settings.getBoolean("LoginChecked", false);
if (loginChecked) {
    userID = settings.getString("userID", "");
    userPW = settings.getString("userPW", "");
    if (userID != null && !userID.isEmpty() && userPW != null && !userPW.isEmpty()) {
        login(userID, userPW);
    } else {
        startActivity(new Intent(getApplication(), Login.class));
        finish();
    }
} else {
    startActivity(new Intent(getApplication(), Login.class));
    finish();
}

 

protected void showJSONResult(String result) {
    if(Integer.parseInt(result) > 0){ // 로그인 정보 일치하면 idx 값 받음
        SharedPreferences.Editor editor = settings.edit();
        editor.putString("idx", result);
        Toast.makeText(Intro.this,"로그인 성공", Toast.LENGTH_SHORT).show();
        startActivity(new Intent(getApplication(), MainActivity.class));
        finish(); // 현재 Activity 를 없애줌
    } else {
        startActivity(new Intent(getApplication(), Login.class));
        finish();
    }
}

 

7. 서버 PHP 코드 작성

loginChk.php

<?php
extract($_POST);

if (isset($_POST['userID']) && isset($_POST['userPW'])) {
    require_once '../config/config.php';
    $c = new MemberClass();

    // get the user by userID and password
    $rs = $c->LoginUserChk($userID,$userPW,$deviceID);
    $user = $c->getUser($userID, $userPW);

    if ($user != false && $rs > 0) {
        // use is found
        echo $user['idx'];
    } else if ($user != false && $rs == -1) {
        // 등록된 폰이 아닙니다. 관리자에게 문의하세요!
        echo -1;
    } else {
        // user is not found with the credentials
        echo 0;
    }
} else {
    echo -2;
}
?>

 

서버 코드 값을 좀 더 세분화하면 등록된 userID가 없는 경우도 안드로이드폰에 반환할 수 있고, 패스워드가 일치하지 않는 경우를 반환할 수도 있다.

이러한 것은 개발자의 몫이다.

 

서버 PHP 와 안드로이드간에 통신 개념부분은 http://link2me.tistory.com/1110 를 참조하면 도움된다.

Bootstrap 기반 로그인 폼 및 로그인 처리(PHP PDO Class 연동) : http://link2me.tistory.com/1402 참조

[부트스트랩] 로그인 폼 및 로그인 처리 (PHP PDO Class 연동)

출처: http://link2me.tistory.com/1402 [소소한 일상 및 업무TIP 다루기]
[부트스트랩] 로그인 폼 및 로그인 처리 (PHP PDO Class 연동)

출처: http://link2me.tistory.com/1402 [소소한 일상 및 업무TIP 다루기]

 

 

 

 

다음에 시간이 되면 안드로이드 회원가입 처리부분을 해볼 생각이다.

 

회원가입이 필요한 분은 https://www.androidhive.info/2012/01/android-login-and-registration-with-php-mysql-and-sqlite 에 명시된 코드를 활용하여 만들어도 될 것이다.

이 코드는 서버에 저장된 DB 데이터를 안드로이드폰 SQLite DB에 저장하는 로직이 추가되어 있으므로 그대로 구현해도 되고, 서버에만 저장하고 저장된 결과를 간단하게 받아서 정상 가입여부만 알 수 있도록 register.php 코드를 수정해도 된다.

Bootstrap 기반 회원가입 폼 및 회원가입 처리(http://link2me.tistory.com/1403) 코드를 참조해서 xml 파일을 수정하고 작성하면 될 것이다.

서버와의 통신에 사용된 HttpURLConnection 부분은 http://link2me.tistory.com/1349 게시글과 첨부파일을 참조하면 된다.

Asynchronous Http Client 를 사용한 서버와의 통신은 http://link2me.tistory.com/1493 게시글을 참조하면 도움된다.

 

첨부파일은 코드 작성에 도움되도록 일부만 발췌하여 첨부한다.

android_Login.zip
다운로드

 

블로그 이미지

Link2Me

,
728x90

부트스트랩 로그인 폼은 https://bootsnipp.com/tags/login 에 많은 예제가 있다.

Preview, HTML, CSS 코드가 있으므로 원하는 걸 선택하거나 수정해서 사용하면 된다.

 

 

input-group 클래스를 사용해서 아이콘 모양이 나오도록 하려면

<link href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" rel="stylesheet">

한줄을 header에 추가해준다.

 

<div class="form-group">
    <label for="password">비밀번호</label>
    <input type="password" id="password" value='' placeholder="비밀번호를 입력하세요" class="form-control" />
</div>

 <div class="input-group">
    <span class="input-group-addon"><i class="fa fa-lock"></i></span>
    <input type="password" id="password" value='' placeholder="비밀번호를 입력하세요" class="form-control" />
</div>

 

style.css 파일 추가사항

.input-group {
    margin-top: 1em;
    margin-bottom: 1em;
}

 

.login-box {
    line-height: 2.3em;
    font-size: 1em;
    color: #aaa;
    margin-top: 1em;
    margin-bottom: 1em;
    padding-top: 0.5em;
    padding-bottom: 0.5em;
}

 

로그인 폼 전체 코드

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>로그인</title>
        <!-- Bootstrap -->
        <link href="../plugin/bootstrap/css/bootstrap.min.css" rel="stylesheet">
        <!-- font awesome -->
        <link href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" rel="stylesheet">
        <!-- Custom style -->
        <link rel="stylesheet" href="../plugin/bootstrap/css/style.css" media="screen" title="no title" charset="utf-8">

        <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
        <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
        <!--[if lt IE 9]>
        <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
        <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
        <![endif]-->
        <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
        <!-- Include all compiled plugins (below), or include individual files as needed -->
        <script src="../plugin/bootstrap/js/bootstrap.min.js"></script>
        <script src="../config/js/login.js"></script>
    </head>
    <body>
        <div class="container">
            <div class="row">
                <div class="col-sm-3">

                    <div class="login-box well">
                        <form accept-charset="UTF-8" role="form" method="post" action="">
                            <legend>로그인</legend>
                            <div class="input-group">
                                <span class="input-group-addon"><i class="fa fa-user"></i></span>
                                <input type="text" id="userid" value='' placeholder="E-mail을 입력하세요" class="form-control" />
                            </div>
                            <div class="input-group">
                                <span class="input-group-addon"><i class="fa fa-lock"></i></span>
                                <input type="password" id="password" value='' placeholder="비밀번호를 입력하세요" class="form-control" />
                            </div>
                            <button type="submit" id="login-submit" class="btn btn-default btn-block"/>로그인</button> <span class='text-center'><a href="" class="text-sm">비밀번호 찾기</a></span>
                            <div class="form-group">
                                <a href="registerForm.php" class="btn btn-default btn-block"> 회원가입</a>
                            </div>
                        </form>
                    </div>

                </div>
            </div>
        </div>
    </body>
</html>

 

login.js

$(function() {
    // 로그인 처리
    $('#login-submit').click(function(e) {
        e.preventDefault();
        if ($("#userid").val() == '') {
            alert('아이디를 입력하세요');
            $("#userid").focus();
            return false;
        }

        if ($("#password").val() == '') {
            alert('비밀번호를 입력하세요');
            $("#password").focus();
            return false;
        }

        $.ajax({
            url : 'loginChk.php',
            type : 'POST',
            data : {
                userid : $('#userid').val(),
                password : $('#password').val()
            },
            dataType : "json",
            success : function(response) {
                if (response.result == 1) {
                    //alert('로그인 성공');
                    location.replace('../index.php'); // 화면 갱신
                    //location.reload(); // 화면 갱신
                } else if (response.result == -2) {
                    alert('입력된 값이 없습니다');
                } else {
                    alert('로그인 실패');
                }
            },
            error : function(jqXHR, textStatus, errorThrown) {
                alert("arjax error : " + textStatus + "\n" + errorThrown);
            }
        });
    });
    
});

 

loginChk.php

<?php
extract($_POST);
if (isset($_POST)) {
    require_once '../config/config.php';
    $c = new MemberClass();

    $user = $c->getUser($userid, $password);

    if ($user != false) {
        if (!isset($_SESSION)) {
            session_start();
        }
        $_SESSION['userID'] = $user['userID'];
        $_SESSION['admin'] = $user['admin'];
        echo json_encode(array('result' => '1'));
    } else {
        echo json_encode(array('result' => '0'));
    }
} else {// 입력받은 데이터에 문제가 있을 경우
    echo json_encode(array('result' => '-2'));
}
?>

 

로그인 처리에 필요한 파일은 모두 위에 열거하였다.

Class 함수에 대한 코드는 http://link2me.tistory.com/1401 에 있다.

테이블 구조는 부트스트랩 회원가입 처리하면서 칼럼 몇개를 더 추가했다.

 

PDO Class 만든 것과 연계하여 부트스트랩 회원가입과 로그인처리하는 기능을 만들고 있다.

로그인 처리하는 부분을 약간 더 손보고 관련 파일을 첨부한다.

비밀번호 찾기, 회원정보 수정 등은 첨부된 파일에 더 추가해서 직접 작성하면 될 것이다.

pdo.zip
다운로드

 

첨부파일은 기존에 올려진 첨부파일과 내용이 모두 다르다고 보면 된다.

 

다음에는 안드로이드 회원가입 처리와 로그인처리 부분을 작성해서 올릴 예정이다.

 

 

 

블로그 이미지

Link2Me

,
728x90

대체 MVC가 뭐지?

MVC(Model-View-Controller)는 소프트웨어 공학에서 사용하는 아키텍쳐 패턴이다.

PHP 코드를 작성할 때 Data 와 Design, Controller 를 분리시켜 만드는 것이다.

개발하는데 복잡하고 어려워도 확장성이 좋고 수정/유지보수하기가 편하다.

 

Framework(프레임워크)는 Web 애플리케이션을 개발할 때 많이 사용하는 기능이나 일상적인 구조를 정리한 것이다. 대부분의 프레임워크는 MVC의 구조를 채용한다.

 

뭐 대충 이렇게 정의하고 있다.

- Model : application 의 정보(데이터) → 데이터의 생성, 접근, 수정, 삭제하는 부분

             데이터의 값과 상태 및 각종 기능을 처리하는 Business Logic 부분과 사용되는 데이터 부분으로 구성

             Controller 에서 건네준 요청을 처리하고 다시 돌려준다.

             모델은 데이터의 가공이나 저장, 비즈니스 로직 등의 프로그램 처리를 담당한다.

- View : User Interface (HTML, CSS 로 만든 정적인 페이지 부분)

            화면 UI 부분이며 사용자게게 응답 처리 결과를 보여준다.

            뷰는 필요에 따라 모델의 데이터 내용을 참조하고 사용자에게 화면을 보여준다.

- Controller : Data 와 Business Logic 사이의 상호동작을 관리한다.

                   Model 과 View 사이에서 request 와 response를 관리하며 동작한다.

                   컨트롤러는 사용자가 조작하면 모델이나 뷰에 필요한 지시를 보낸다.

 

 

PHP MVC Frmaework 의 대표적인 것이 Laravel(라라벨), CodeIgniter(코드이그나이터) 라는데 아직 사용해본 적은 없다.

그누보드, 제로보드, KIMSQRB 는 프레임웍이라기 보다는 CMS툴에 가깝다고 봐야 한다.

 

프레임웍을 만들정도까지 실력을 키우려면 한참 배워야 한다.

이제 그런 구조(패턴)을 고려해서 PHP 코드 작성을 해보고 있는 중이다.

 

게시글 http://link2me.tistory.com/1398 , http://link2me.tistory.com/1399 에 첨부된 파일과 비교해서 수정된 부분이 어떤 것인지 살펴보면 도움될 것이다.

본 코드는 테스트용도이므로 Class를 더 세분화해서 분리하고 Class 이름을 정해서 추가해나가면 된다.

 

아래 코드와 login.php, register.php 파일이 포함된 압축파일을 첨부한다.

pdo.zip
다운로드

 

 

 

 

 

<?php
//****************
// DB 정보 입력
//****************
define('_DBHOST','localhost');
define('_DBUSER','android'); // DB 사용자
define('_DBPASS', '!android@'); // DB 패스워드
define('_DBNAME','android'); // DB 명
define('_DBTYPE','mysql'); // 데이터베이스 종류
define('_DSN',_DBTYPE.':host='._DBHOST.';dbname='._DBNAME.';charset=utf8');

//*****************************
// 파일 디렉토리
//*****************************
define('_CONFIG_DIR',__DIR__);
define('_CLASS_DIR',_CONFIG_DIR.'/Class/');

//***************************
//클래스 파일 불러오기
//***************************
require_once _CLASS_DIR.'DBConClass.php'; // DB 연결 클래스
require_once _CLASS_DIR.'MemberClass.php';

?>

<?php
class DBConClass{
    protected $db; // 변수를 선언한 클래스와 상속받은 클래스에서 참조할 수 있다.

    public function __construct(){
        $this->dbConnect();

        // construct 메소드는 객체가 생성(인스턴스화)될 때 자동으로 실행되는 특수한 메소드다.

    }

    private function dbConnect() {
        try{
            // MySQL PDO 객체 생성
            $this->db = new PDO(_DSN, _DBUSER, _DBPASS);
            $this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
            $this->db->setAttribute(PDO::ATTR_EMULATE_PREPARES, FALSE);
        } catch(PDOException $ex){
            die("오류 : ".$ex->getMessage());
        }
    }
}
?>

<?php
class MemberClass extends DBConClass {
    // 회원 정보 신규 입력
    public function storeUser($userID, $userNM, $email, $password, $telNO, $mobileNO) {
        $hash = $this->hashSSHA($password);
        $encrypted_password = $hash['encrypted']; // encrypted password
        $salt = $hash['salt']; // salt

        try{
            $this->db->beginTransaction();
            $sql = "INSERT INTO members(userID, userNM, email, passwd, salt, telNO,mobileNO,created_at) VALUES(:userID,:userNM,:email,:passwd,:salt,:telNO,:mobileNO,:created_at)";
            $stmt = $this->db->prepare($sql);
            $stmt->bindValue(':userID',$userID,PDO::PARAM_STR);
            $stmt->bindValue(':userNM',$userNM,PDO::PARAM_STR);
            $stmt->bindValue(':email',$email,PDO::PARAM_STR);
            $stmt->bindValue(':passwd',$encrypted_password,PDO::PARAM_STR);
            $stmt->bindValue(':salt',$salt,PDO::PARAM_STR);
            $stmt->bindValue(':telNO',$telNO,PDO::PARAM_STR);
            $stmt->bindValue(':mobileNO',$mobileNO,PDO::PARAM_STR);
            $stmt->bindValue(':created_at',date("YmdHis"));
            $result = $stmt->execute();
            $this->db->commit();
        } catch (PDOException $pex) {
            $this->db->rollBack();
            echo "에러 : ".$pex->getMessage();
        }
        // check for successful store
        if ($result) {
            $stmt = $this->db->prepare("SELECT * FROM members WHERE userID = :userID");
            $stmt->bindValue(':userID', $userID, PDO::PARAM_STR);
            $stmt->execute();
            $user = $stmt->fetch(PDO::FETCH_ASSOC);

            return $user;
        } else {
            return false;
        }
    }

    // 로그인 체크
    public function getUser($userID, $password) {
        $sql = "SELECT * FROM members WHERE userID=:userID";
        $stmt = $this->db->prepare($sql);
        $stmt->bindValue(':userID', $userID);
        $stmt->execute();

        if ($user=$stmt->fetch()) {
            // verifying user password
            $salt = $user['salt'];
            $encrypted_password = $user['passwd'];
            $hash = $this->checkhashSSHA($salt, $password);
            if ($encrypted_password == $hash) {
                // user authentication details are correct
                return $user;
            }
        } else {
            return NULL;
        }
    }

    // 안드로이드/아이폰 로그인 체크
    public function LoginUserChk($userID,$password,$deviceID){
        if(empty($userID) || empty($password)){
            return 0;
        } else {
            $user = $this->getUser($userID, $password);
            if($user['idx']>0){
                // 장치 일련번호 체크
                if($user['phoneSE'] == NULL){
                    // 신규 장비번호 입력(최초 로그인)
                    $this->LoginUserEquipInput($userID,$deviceID);
                    return $user['idx'];
                } else {
                    if($user['phoneSE'] === $deviceID){
                        return 1; // 일련번호 일치
                    } else {
                        return -1; //일련번호 불일치
                    }
                }
            } else {
                return 0; //계정오류
            }
        }

    }

    // 장치번호 업데이트
    public function LoginUserEquipInput($userID,$deviceID){
        if(strlen($deviceID)>0 && is_numeric($deviceID)){ // 안드로이드폰
            $ostype = 2;
        } else if(strlen($deviceID)>30){ // 아이폰
            $ostype = 1;
        } else { // 기타
            $ostype = 0;
        }

        try{
            $this->db->beginTransaction();
            $sql='update members set phoneSE=:phoneSE, OStype=:OStype where userID=:userID';
            $stmt = $this->db->prepare($sql);
            $stmt->bindValue(':phoneSE',$deviceID,PDO::PARAM_STR);
            $stmt->bindValue(':OStype',$ostype,PDO::PARAM_INT);
            $stmt->bindValue(':userID',$userID,PDO::PARAM_STR);
            $status = $stmt->execute();
            $this->db->commit();
            if($status == true){
                return 1;
            } else {
                return 0;
            }
        }catch (PDOException $pex) {
            $this->db->rollBack();
            echo "에러 : ".$pex->getMessage();
        }
    }//end

    // 장치번호 초기화
    public function EquipReset($userID){
        try{
            $this->db->beginTransaction();
            $ostype = 0;
            $sql='update members set phoneSE=NULL,OStype=:OStype where userID=:userID';
            $stmt = $this->db->prepare($sql);
            $stmt->bindValue(':OStype',$ostype,PDO::PARAM_INT);
            $stmt->bindValue(':userID',$userID,PDO::PARAM_STR);
            $status = $stmt->execute();
            $this->db->commit();
            if($status == true){
                return 1;
            } else {
                return 0;
            }
        } catch (PDOException $pex) {
            $this->db->rollBack();
            echo "에러 : ".$pex->getMessage();
        }
    }//end

    // 회원 가입 여부 체크
    public function isUserExisted($userID) {
        $stmt = $this->db->prepare("SELECT userID from members WHERE userID=:userID");
        $stmt->bindValue(':userID',$userID,PDO::PARAM_STR);
        $stmt->execute();
        if ($stmt->rowCount() > 0) {
            return true;
        } else {
            return false;
        }
    }

    // 회원 정보 삭제
    public function deleteUser($userID){
        try{
            $this->db->beginTransaction();
            $stmt = $this->db->prepare("delete FROM members WHERE userID=:userID");
            $stmt->bindValue(':userID',$userID,PDO::PARAM_STR);
            $stmt->execute();
            $this->db->commit();
        }catch (PDOException $pex) {
            $this->db->rollBack();
            echo "에러 : ".$pex->getMessage();
        }
    }

    public function hashSSHA($password) {
        $salt = sha1(rand());
        $salt = substr($salt, 0, 10);
        $encrypted = base64_encode(sha1($password . $salt, true) . $salt);
        $hash = array("salt" => $salt, "encrypted" => $encrypted);
        return $hash;
    }

    public function checkhashSSHA($salt, $password) {
        $hash = base64_encode(sha1($password . $salt, true) . $salt);
        return $hash;
    }
}
?>

 

블로그 이미지

Link2Me

,
728x90

윈도우 기반 AutoSet9 에서 테스트를 하다보니 계속 driver 를 못찾는다는 에러 메시지를 출력한다.

원인은 PDO(PHP Data Object) 설정이 안되어 있어서다.

 

PHP에는 다양한 데이터베이스를 쉽게 이용할 수 있도록 PDO라는 확장모듈이 포함되어 있다.

PDO(PHP Data Object) 동작이 되도록 설정하는 방법은 간단하다.

 

1. conf/php.ini 파일을 열어서 아래와 같이 주석을 제거한다.

- php_pdo_mysql.dll 만 주석제거 해도 되는데 PostgreSQL 도 사용하게 될지 몰라서 같이 주석 제거를 했다.

 

 

 

2. php.ini 수정사항을 반영하기 위해서는 Apache Web서버를 재기동해야 한다.

 

 

웹서버 멈춤을 눌러서 웹서버를 종료시켰다가 웹서버 시작을 눌러서 재실행되도록 한다.

 

 

3. phpinfo(); 로 설정된 상태가 반영되었는지 확인한다.

 

이렇게 해주면 PDO 방식으로 코딩 테스트를 할 수 있다.

 

AutoSet10 (PHP 7.0 + MariaDB) 도 동일하게 설정하면 된다.

블로그 이미지

Link2Me

,
728x90

<?php
// 절차지향 방식
$sql = "SELECT * FROM table WHERE field1 = $field1 AND field2 = '$field2' ORDER BY id";
$stmt = mysqli_query($db,$sql);

// PDO (정석대로)
$sql = "SELECT * FROM table WHERE field1 = ? AND field2 = ? ORDER BY id";
$stmt = $db->prepare($sql);
$stmt->bindParam(1, $field1, PDO::PARAM_INT);
$stmt->bindParam(2, $field2, PDO::PARAM_STR);
$stmt->execute();

// PDO (그나마 좀 간소화시킨 문법)
$sql = "SELECT * FROM table WHERE field1 = ? AND field2 = ? ORDER BY id";
$stmt = $db->prepare($sql);
$stmt->execute(array($field1, $field2));
?>

 

PDO(PHP Data Object) 방식 함수를 사용하여 만든 MemberClass 예제다.

<?php
class MemberClass {
    // 회원 정보 신규 입력
    public function storeUser($userID$userNM$email$password$telNO$mobileNO) {
        global $db;
 
        $hash = $this->hashSSHA($password);
        $encrypted_password = $hash['encrypted']; // encrypted password
        $salt = $hash['salt']; // salt
 
        try{
            $db->beginTransaction();
            $sql = "INSERT INTO members(userID, userNM, email, passwd, salt, telNO,mobileNO,created_at) VALUES(:userID,:userNM,:email,:passwd,:salt,:telNO,:mobileNO,:created_at)";
            $stmt = $db->prepare($sql);
            $stmt->bindValue(':userID',$userID,PDO::PARAM_STR);
            $stmt->bindValue(':userNM',$userNM,PDO::PARAM_STR);
            $stmt->bindValue(':email',$email,PDO::PARAM_STR);
            $stmt->bindValue(':passwd',$encrypted_password,PDO::PARAM_STR);
            $stmt->bindValue(':salt',$salt,PDO::PARAM_STR);
            $stmt->bindValue(':telNO',$telNO,PDO::PARAM_STR);
            $stmt->bindValue(':mobileNO',$mobileNO,PDO::PARAM_STR);
            $stmt->bindValue(':created_at',date("YmdHis"));
            $result = $stmt->execute();
            $db->commit();
        } catch (PDOException $pex) {
            $db->rollBack();
            echo "에러 : ".$pex->getMessage();
        }
        // check for successful store
        if ($result) {
            $stmt = $db->prepare("SELECT * FROM members WHERE userID = :userID");
            $stmt->bindValue(':userID'$userID, PDO::PARAM_STR);
            $stmt->execute();
            $user = $stmt->fetch(PDO::FETCH_ASSOC);
 
            return $user;
        } else {
            return false;
        }
    }
 
    // 로그인 체크
    public function getUser($userID$password) {
        global $db;
        $sql = "SELECT * FROM members WHERE userID=:userID";
        $stmt = $db->prepare($sql);
        $stmt->bindValue(':userID'$userID);
        $stmt->execute();
 
        if ($user=$stmt->fetch()) {
            // verifying user password
            $salt = $user['salt'];
            $encrypted_password = $user['passwd'];
            $hash = $this->checkhashSSHA($salt$password);
            if ($encrypted_password == $hash) {
                // user authentication details are correct
                return $user;
            }
        } else {
            return NULL;
        }
    }
 
    // 안드로이드/아이폰 로그인 체크
    public function LoginUserChk($userID,$password,$deviceID){
        global $db;
        if(empty($userID|| empty($password)){
            return 0;
        } else {
            $user = $this->getUser($userID$password);
            if($user['idx']>0){
                // 장치 일련번호 체크
                if($user['phoneSE'== NULL){
                    // 신규 장치번호 입력(최초 로그인)
                    $this->LoginUserEquipInput($userID,$deviceID);
                    return $user['idx'];
                } else {
                    if($user['phoneSE'=== $deviceID){
                        return 1// 일련번호 일치
                    } else {
                        return -1//일련번호 불일치
                    }
                }
            } else {
                return 0//계정오류
            }
        }
 
    }
 
    // 장치번호 업데이트
    public function LoginUserEquipInput($userID,$deviceID){
        global $db;
        if(strlen($deviceID)>0 && is_numeric($deviceID)){ // 안드로이드폰
            $ostype = 2;
        } else if(strlen($deviceID)>30){ // 아이폰
            $ostype = 1;
        } else { // 기타
            $ostype = 0;
        }
 
        try{
            $db->beginTransaction();
            $sql='update members set phoneSE=:phoneSE, OStype=:OStype where userID=:userID';
            $stmt = $db->prepare($sql);
            $stmt->bindValue(':phoneSE',$deviceID,PDO::PARAM_STR);
            $stmt->bindValue(':OStype',$ostype,PDO::PARAM_INT);
            $stmt->bindValue(':userID',$userID,PDO::PARAM_STR);
            $status = $stmt->execute();
            $db->commit();
            if($status == true){
                return 1;
            } else {
                return 0;
            }
        }catch (PDOException $pex) {
            $db->rollBack();
            echo "에러 : ".$pex->getMessage();
        }
    }//end
 
    // 장치번호 초기화
    public function EquipReset($userID){
        global $db;
        try{
            $db->beginTransaction();
            $ostype = 0;
            $sql='update members set phoneSE=NULL,OStype=:OStype where userID=:userID';
            $stmt = $db->prepare($sql);
            $stmt->bindValue(':OStype',$ostype,PDO::PARAM_INT);
            $stmt->bindValue(':userID',$userID,PDO::PARAM_STR);
            $status = $stmt->execute();
            $db->commit();
            if($status == true){
                return 1;
            } else {
                return 0;
            }
        } catch (PDOException $pex) {
            $db->rollBack();
            echo "에러 : ".$pex->getMessage();
        }
    }//end
 
    // 회원 가입 여부 체크
    public function isUserExisted($userID) {
        global $db;
        $stmt = $db->prepare("SELECT userID from members WHERE userID=:userID");
        $stmt->bindValue(':userID',$userID,PDO::PARAM_STR);
        $stmt->execute();
        if ($stmt->rowCount() > 0) {
            return true;
        } else {
            return false;
        }
    }
 
    // 회원 정보 삭제
    public function deleteUser($userID){
        global $db;
        try{
            $db->beginTransaction();
            $stmt = $db->prepare("delete FROM members WHERE userID=:userID");
            $stmt->bindValue(':userID',$userID,PDO::PARAM_STR);
            $stmt->execute();
            $db->commit();
        }catch (PDOException $pex) {
            $db->rollBack();
            echo "에러 : ".$pex->getMessage();
        }
    }
 
    public function hashSSHA($password) {
        $salt = sha1(rand());
        $salt = substr($salt010);
        $encrypted = base64_encode(sha1($password . $salttrue) . $salt);
        $hash = array("salt" => $salt"encrypted" => $encrypted);
        return $hash;
    }
 
    public function checkhashSSHA($salt$password) {
        $hash = base64_encode(sha1($password . $salttrue) . $salt);
        return $hash;
    }
}
?>

 

 

안드로이드와 연동하는 login.php, register.php 파일 예제는 첨부파일을 참조하면 된다.

pdo.zip
다운로드

 

조만간에 부트스트랩 registerForm.php 와 loginForm.php 파일을 간단하게 만들고, 관련 처리 기능을 만들 예정이다.

 

 

PHP 7.0 에서 mysql 기존방식은 지원을 아예 안하고 mysqli  및 pdo를 지원하므로 PDO 방식으로 코딩하는 방법을 계속 연습해두고 기능을 하나 하나 익혀두는게 나을거 같아서 정리를 해보는 중이다.

 

수정사항(2017.11.30)

회원정보 삭제 테스트를 하다보니 원하는 결과가 나오지 않아서 로직을 수정 보완했다.

// 회원 가입 여부 체크
public function isUserExisted($userID) {
    $stmt = $this -> db -> prepare("SELECT count(userID) from members WHERE userID=:userID");
    $stmt -> bindValue(':userID', $userID, PDO::PARAM_STR);
    $stmt -> execute();
    if ($row = $stmt -> fetch()) {
        return $row[0];
        // 미가입이면 0 반환, 가입이면 1 반환
    } else {
        return -1;
    }
}

// 회원 정보 삭제 : Move
public function deleteUser($userID) {
    if ($this -> isUserExisted($userID) > 0) {
        try {
            $this -> db -> beginTransaction();
            $stmt = $this -> db -> prepare("delete FROM members WHERE userID=:userID");
            $stmt -> bindValue(':userID', $userID, PDO::PARAM_STR);
            $stmt -> execute(); // 삭제할 데이터의 존재 유무에 상관없이 실행한다.
            $this -> db -> commit();
        } catch (PDOException $pex) {
            $this -> db -> rollBack();
            echo "에러 : " . $pex -> getMessage();
        }
        return 1; // 삭제 성공
    } else {
        return 0; // 삭제할 데이터가 없음
    }
}

 

블로그 이미지

Link2Me

,
728x90

PDO(PHP Data Object) 방식으로 DB을 연결하는 방법이다.

- PDO는 여러가지 DBMS를 제어하는 방법을 표준화시켜 다양한 DB(Oracle, PostgrSQL, MySQL 등)를 동일한 방법으로 제어할 수 있다.

- PHP 7.0 에서는 MySQL API 지원 중단, MySQLi 절차식 API 및 객체 API 제공, PDO 객체 API 제공


사용법

require_once 'DBController.php';
require_once 'loginClass.php';
$c=new LoginClass;

LoginClass 만 객체 생성해주면 부모클래스인 DBController 클래스는 자동으로 인식된다.


<?php
//****************
// DB 정보 입력
// define('상수명', '값', true);
//****************
define('_DBHOST','localhost');
define('_DBUSER','address_root'); // DB 사용자
define('_DBPASS', 'autoset'); // DB 패스워드
define('_DBNAME','address'); // DB 명
define('_DBTYPE','mysql'); // 데이터베이스 종류
define('_DSN',_DBTYPE.':host='._DBHOST.';dbname='._DBNAME.';charset=utf8');
?>

 <?php
class DBController {
    protected $db;

    public function __construct() {
        $this->db = $this -> dbConnect();
        // construct 메소드는 객체가 생성(인스턴스화)될 때 자동으로 실행되는 특수한 메소드다.
    }

    private function dbConnect() {
        require_once 'config.php';
        try { // MySQL PDO 객체 생성
            $conn = new PDO(_DSN, _DBUSER, _DBPASS);
            $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
            $conn->setAttribute(PDO::ATTR_EMULATE_PREPARES, FALSE);
        } catch(PDOException $ex) {
            die("오류 : " . $ex -> getMessage());
        }
        return $conn;
    }
?>

 <?php
class LoginClass extends DBController {
    // 회원 정보 신규 입력
    public function storeUser($userID, $userNM, $password, $mobileNO) {
        $hash = $this -> hashSSHA($password);
        $encrypted_password = $hash['encrypted']; // encrypted password
        $salt = $hash['salt']; // salt
        $regdate = date("YmdHis");

        $key = "userID, userNM, passwd, salt, regdate";
        $val = "$userID,$userNM,$encrypted_password,$salt,$regdate";
        $result = $this -> getDbInsert('members', $key, $val); //
DBController에서 별도 생성 함수
        // check for successful store
        if ($result) {
            $user = $this -> getUser($userID, $password);
            $rs = $this -> storeUserDetail($user['idx'], $userNM, $userID, $mobileNO);
            if ($rs == 1) {
                return $user;
            } else {
                return -1;
            }
        } else {
            return -1; // 0
        }
    }

    // 회원 세부 정보 입력
    public function storeUserDetail($relatedidx, $userNM, $email, $mobileNO) {
        $regdate = date("YmdHis");
        $key = 'relatedidx,name,email,mobileNO,modifydate'; // 칼럼 정확한지 체크
        $val = "$relatedidx,$userNM,$email,$mobileNO,$regdate";
        $this -> getDbInsert('member_data', $key, $val);
    }

    // 회원정보 반환
    public function getUser($userID, $password) {
        $sql = "SELECT * FROM members WHERE userID=:userID";
        $stmt = $this -> db -> prepare($sql);
        $stmt -> bindValue(':userID', $userID);
        $stmt -> execute();

        if ($user = $stmt -> fetch()) {
            // verifying user password
            $salt = $user['salt'];
            $encrypted_password = $user['passwd'];
            $hash = $this -> checkhashSSHA($salt, $password);
            if ($encrypted_password == $hash) {
                // user authentication details are correct
                return $user;
            }
        } else {
            return NULL;
        }
    }

    // 회원 가입 여부 체크
    public function isUserExisted($userID) {
        $stmt = $this -> db -> prepare("SELECT count(userID) from members WHERE userID=:userID");
        $stmt -> bindValue(':userID', $userID, PDO::PARAM_STR);
        $stmt -> execute();
        if ($row = $stmt -> fetch()) {
            return $row[0];
            // 미가입이면 0 반환, 가입이면 1 반환
        } else {
            return -1;
        }
    }

    // 회원정보 수정
    public function updateUser($idx, $userNM, $mobileNO) {
        if(isset($idx) && !empty($idx)){
            $this->getDbUpdate('members', "userNM=?", array($userNM), "idx=".$idx);
            $set = "name=?,mobileNO=?";
            $params = array($userNM,$mobileNO);
            $this->getDbUpdate('member_data', $set, $params, 'relatedidx='.$idx);
            return 1;
        } else {
            return 0;
        }
    }

    // 회원 정보 삭제 : Move
    public function deleteUser($userID) {
        if ($this -> isUserExisted($userID) > 0) {
            try {
                $this -> db -> beginTransaction();
                $stmt = $this -> db -> prepare("delete FROM members WHERE userID=:userID");
                $stmt -> bindValue(':userID', $userID, PDO::PARAM_STR);
                $stmt -> execute(); // 삭제할 데이터의 존재 유무에 상관없이 실행한다.
                $this -> db -> commit();
            } catch (PDOException $pex) {
                $this -> db -> rollBack();
                echo "에러 : " . $pex -> getMessage();
            }
            return 1; // 삭제 성공
        } else {
            return 0; // 삭제할 데이터가 없음
        }
    }

    // 안드로이드/아이폰 로그인 체크
    public function LoginUserChk($userID, $password, $deviceID) {
        if (empty($userID) || empty($password)) {
            return 0;
        } else {
            $user = $this -> getUser($userID, $password);
            if ($user['idx'] > 0) {
                // 장치 일련번호 체크
                if ($user['phoneSE'] == NULL) {
                    // 신규 장비번호 입력(최초 로그인)
                    $this -> LoginUserEquipInput($userID, $deviceID);
                    return $user['idx'];
                } else {
                    if ($user['phoneSE'] === $deviceID) {
                        return 1;
                        // 일련번호 일치
                    } else {
                        return -1;
                        //일련번호 불일치
                    }
                }
            } else {
                return 0;
                //계정오류
            }
        }

    }

    // 장치번호 업데이트
    public function LoginUserEquipInput($userID, $deviceID) {
        if (strlen($deviceID) > 0 && is_numeric($deviceID)) {// 안드로이드폰
            $ostype = 2;
        } else if (strlen($deviceID) > 30) {// 아이폰
            $ostype = 1;
        } else {// 기타
            $ostype = 0;
        }

        try {
            $this -> db -> beginTransaction();
            $sql = 'update members set phoneSE=:phoneSE, OStype=:OStype where userID=:userID';
            $stmt = $this -> db -> prepare($sql);
            $stmt -> bindValue(':phoneSE', $deviceID, PDO::PARAM_STR);
            $stmt -> bindValue(':OStype', $ostype, PDO::PARAM_INT);
            $stmt -> bindValue(':userID', $userID, PDO::PARAM_STR);
            $status = $stmt -> execute();
            if ($status == true) {
                $this -> db -> commit();
                return 1;
            } else {
                return 0;
            }
        } catch (PDOException $pex) {
            $this -> db -> rollBack();
            echo "에러 : " . $pex -> getMessage();
        }
    }//end

}
?>


추가 (2018.04.09)

검색하다보니 https://gist.github.com/kijin/5947026 에 ezPDO 라는 Class 파일을 만든 고수분이 있네.


블로그 이미지

Link2Me

,
728x90

PDO 방식으로 MySQL 과 연동하여 데이터를 가져오는 걸 테스트했다.

사용자 정의 함수를 만들어서 연동하는 방법이다.

사용자 함수 CustomFunction($ItemName,$Quantity) 의 결과가 있을 경우에는 데이터를 반환하고, 없으면 false 로 결과 반환을 한다.

true, fasle 로 처리했으므로 if($result = CustomFunction($ItemName,$Quantity)) 문으로 처리하면 원하는 결과를 얻을 수 있다.


<?php
require_once 'pdoconn.php';
// SELECT
$ItemName ='수박';
$Quantity = 500;
if($result = CustomFunction($ItemName,$Quantity)){
    // Fetch 모드를 설정 및 1 row 씩 가져오기
    while($row= $result->fetch(PDO::FETCH_BOTH)) {
        echo $row[0].' | ';
        echo $row[1].' | ';
        echo $row[2].' | ';
        echo $row[3].'<br />';
    }
}
$db = null;

function CustomFunction($ItemName,$Quantity){
    global $db;
    $sql='select uid,ItemName,Price,Quantity from table_items where ItemName=:ItemName and Quantity=:Quantity';
    $stmt = $db->prepare($sql);
    $stmt->bindParam(':ItemName', $ItemName);
    $stmt->bindParam(':Quantity', $Quantity);
    $stmt->execute();
    if($stmt->fetch(PDO::FETCH_BOTH)){
        return $stmt;
    } else {
        return false;
    }
}
?>


함수의 개수가 많아지면 함수명이 중복될 수도 있고 관리가 어려워질 수 있다.

이제 Class 화를 해보자.

<?php
class TestClass
{
    function CustomFunction($ItemName,$Quantity){
        global $db;
        $sql='select uid,ItemName,Price,Quantity from table_items where ItemName=:ItemName and Quantity=:Quantity';
        $stmt = $db->prepare($sql);
        $stmt->bindParam(':ItemName', $ItemName);
        $stmt->bindParam(':Quantity', $Quantity);
        $stmt->execute();
        if($stmt->fetch(PDO::FETCH_BOTH)){
            return $stmt;
        } else {
            return false;
        }
    }
}
?> 

bind_param 은 mysqli_stmt 방식이고, bindParam은 PDOStatement 방식이다.


TestClass 에 있는 함수를 이용하는 예제다.

처음에 연동했던 방식과 비교해서 어떤 부분이 달라졌는지만 살펴보면 알 수 있다.

<?php
require_once 'pdoconn.php';
require_once 'pdoClass.php';
$test = new TestClass;

$ItemName ='수박';
$Quantity = 500;
if($result = $test->CustomFunction($ItemName,$Quantity)){
    // Fetch 모드를 설정 및 1 row 씩 가져오기
    while($row= $result->fetch(PDO::FETCH_BOTH)) {
        echo $row[0].' | ';
        echo $row[1].' | ';
        echo $row[2].' | ';
        echo $row[3].'<br />';
    }
}
$db = null;
?> 


위 방식에는 문제가 있더라.

결과 개수가 다르게 나온다.

제대로 처리하기 위해서는 ....

<?php
include_once 'dbinfo.php';
try{
    // MySQL PDO 객체 생성
    $db = new PDO('mysql:host='.$db['host'].';dbname='.$db['name'].';charset=utf8', $db['user'], $db['pass']);
    // 에러 출력
    $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
catch(Exception $e) {
    echo 'Failed to obtain database handle : '.$e->getMessage();
}
?>

<?php
class TestClass
{
    function CustomFunction($ItemName,$Quantity){
        global $db;
        $sql='select uid,ItemName,Price,Quantity from table_items where ItemName=:ItemName and Quantity=:Quantity';
        $stmt = $db->prepare($sql);
        $stmt->bindParam(':ItemName', $ItemName);
        $stmt->bindParam(':Quantity', $Quantity);
        $stmt->execute();
        if($this->CustomFunctionCount($ItemName,$Quantity)>0){
            return $stmt;
        } else {
            return false;
        }
    }

    function CustomFunctionCount($ItemName,$Quantity){
        global $db;
        $sql='select count(*) from table_items where ItemName=:ItemName and Quantity=:Quantity';
        $stmt = $db->prepare($sql);
        $stmt->bindParam(':ItemName', $ItemName);
        $stmt->bindParam(':Quantity', $Quantity);
        $stmt->execute();
        return $stmt->fetchColumn();
    }

}
?>

<?php
require_once 'pdoconn.php';
require_once 'pdoClass.php';
$test = new TestClass;

$ItemName ='수박';
$Quantity = 500;

//echo $test->CustomFunctionCount($ItemName,$Quantity).'<br />';
if($result = $test->CustomFunction($ItemName,$Quantity)){
    // Fetch 모드를 설정 및 1 row 씩 가져오기
    while($row= $result->fetch(PDO::FETCH_BOTH)) {
        echo $row[0].' | ';
        echo $row[1].' | ';
        echo $row[2].' | ';
        echo $row[3].'<br />';
    }
}
$db = null;
?>


'Web 프로그램 > PDO' 카테고리의 다른 글

[PHP] PDO MemberClass  (0) 2017.11.16
[PHP] PDO DB 연결방법  (0) 2017.11.16
MySQL 접속방식 PDO 접속방식으로 변환  (0) 2017.07.28
PDO(PHP Data Objects) 개념 및 사용법  (0) 2017.07.25
[리눅스] PDO-MYSQL 모듈 설치  (0) 2017.06.12
블로그 이미지

Link2Me

,
728x90

PDO 접속방식으로 변경하기 위해서 기존 MySQL 방식으로 만든 함수를 PDO 접속형식으로 변경하는 방법이다.


MySQL 

 PDO

 $result=mysql_query($sql);

 $stmt = $db->prepare($sql);

 $stmt->execute();

 $row=mysql_fetch_row($result)

 $row= $stmt->fetch(PDO::FETCH_NUM)

 $row=mysql_fetch_array($result)

 $row= $stmt->fetch(PDO::FETCH_BOTH)

 mysql_num_rows($result)

 $sql='select count(*) from tableName order by uid';
 $stmt = $db->query($sql);
 $num_rows = $stmt->fetchColumn();

 if($result=mysql_query($sql)){
     $row=mysql_fetch_row($result);
     return $row[0];
 } else {
     return 0;
 }

 $stmt = $db->prepare($sql);
 $stmt->execute();
 if($row=$stmt->fetch()){
     return $row[0];
 } else {
     return 0;
 }

 if($result = mysql_query($sql)){
     $row = mysql_fetch_row($result);
     if($row[0] == NULL){
         return 0;
     } else {
         return $row[0];
     }
 } else {
     return 0;
 }

 $stmt = $db->prepare($sql);
 $stmt->execute();
 if($row=$stmt->fetch()){
     if($row[0] == NULL){
         return 0;
     } else {
         return $row[0];
     }
 } else {
     return 0;
 }



블로그 이미지

Link2Me

,
728x90

mysql 확장 기능은 마침내 PHP 7에서 제거되었다.
PHP는 급격하게 성장하며 더 나은 프로그래밍 언어가 되고 있다.
PDO를 사용하는 것은 객체지향과 재사용 가능한 애플리케이션의 데이터베이스 층을 만드는 첫단계이다.

PDO(PHP Data Objects)란 여러가지 데이터베이스를 제어하는 방법을 표준화시킨 것이다.
데이터베이스는 다양한 종류가 있다. PDO 를 사용하면 동일한 방법으로 데이터베이스를 제어할 수 있다.

 

PDO를 이용하기 위한 준비사항

윈도우 Autoset9 (Apache + PHP + MySQL) 에서 PDO를 이용하려면 php.ini 파일에서 주석처리된 것을 없앤다. extension=php_pdo_mysql.dll
;extension=php_pdo_oci.dll
;extension=php_pdo_odbc.dll
extension=php_pdo_pgsql.dll
extension=php_pdo_sqlite.dll

 

Autoset9을 다시 실행하면 phpinfo() 에서 설치된 것을 확인할 수 있다.

 

PDO 를 이용하기 위한 준비가 되었다.

PHP 5.5 이상에서 이용 가능하다. AutoSet9 을 설치하면 PHP 5.6.0 버전을 이용한다.

CentOS 6.6 에서 APM 소스 설치방법으로 하는 방법도 가능하더라. 소스 설치 스크립트를 만들어 두면 편리하다.

 

PDO 기본 사용법

$variable = new PDO(DSN, 사용자명, PW);
$variable = null; // PDO 접속 종료
DSN : 접두사:host=localhost;dbname=DB명;charset=utf8;
접두사 : DB의 종류(MySQL, Pgsql, Sqlite)

 

=== dbinfo.php ===

<?php
$db['host'] = "localhost";
$db['name'] = "address";
$db['user'] = "address_root";
$db['pass'] = "autoset";
$db['port'] = "3306";

//use mysql;
//create user address_root@localhost identified by 'autoset';
//grant all privileges on address.* to address_root@localhost;  -- 사용자 권한 부여
//flush privileges;    -- // 변경된 내용을 메모리에 반영(권한 적용)
?>

 

객체 생성 : try catch 문에서 생성한다.

===  pdoconn.php ===

<?php
include_once 'dbinfo.php';
try{
    // MySQL PDO 객체 생성
    $dbconn = new PDO('mysql:host='.$db['host'].';dbname='.$db['name'].';charset=utf8', $db['user'], $db['pass']);

    // 에러 출력
    $dbconn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
catch(Exception $e) {
    echo 'Failed to obtain database handle : '.$e->getMessage();
}
?>

 

new PDO DSN 입력할 때 실수하면 에러가 발생한다.

 

 

PDO SQL 실행

 

<?php
require_once 'pdoconn.php';
// SELECT
$sql="select uid,ItemName,Price,Quantity from table_items";
$stmt = $dbconn->prepare($sql);
$stmt->execute();

// Fetch 모드를 설정
$stmt->setFetchMode(PDO::FETCH_BOTH); // PDO::FETCH_ASSOC, PDO::FETCH_NUM
// 1 row 씩 가져오기
while($row= $stmt->fetch()) {
    echo $row[0].' | ';
    echo $row[1].' | ';
    echo $row[2].' | ';
    echo $row[3].'<br />';
}
?>

 

검색조건을 추가해보자.

위의 내용과 비교해서 달라진 점이 무엇인지 확인해보면

검색조건이 where ItemName=:ItemName 로 되어 있고, $stmt->bindParam(':ItemName', $ItemName); 한줄을 추가했고, 실제 검색어 Value 인 $ItemName ='사과'; 을 한줄 추가했다.

<?php
require_once 'pdoconn.php';
// SELECT
$sql="select uid,ItemName,Price,Quantity from table_items where ItemName=:ItemName";
$stmt = $dbconn->prepare($sql);
$stmt->bindParam(':ItemName', $ItemName); // 변수에 바인딩하기 위해 bindParam을 사용
$ItemName ='사과';
$stmt->execute();

// Fetch 모드를 설정 및 1 row 씩 가져오기
while($row= $stmt->fetch(PDO::FETCH_BOTH)) {
    echo $row[0].' | ';
    echo $row[1].' | ';
    echo $row[2].' | ';
    echo $row[3].'<br />';
}
?>
 

 

$stmt->bindValue(':id', '1', PDO::PARAM_STR);

$stmt->bindValue(':ItemName', '사과');

'1', '사과' 처럼 값을 직접 넣을 때는 bindValue 를 사용해야 한다.

 

$stmt->bindParam(':ItemName', '사과'); // bindParam 을 사용하면 에러가 발생한다.

 

 

이제 bindParam 과 bindValue 차이를 확인할 수 있는 예제를 한번 보자.

아래의 첫번째 예제는 '사과'에 대한 결과를 반환하고, 아래의 두번째 예제는 '복숭아'에 대한 결과를 반환한다.

<?php
require_once 'pdoconn.php';
// SELECT
$ItemName ='사과';
$sql="select uid,ItemName,Price,Quantity from table_items where ItemName=:ItemName";
$stmt = $dbconn->prepare($sql);
$stmt->bindValue(':ItemName', $ItemName); // 변수에 바인딩하기 위해 bindParam을 사용
$ItemName ='복숭아'; // 이건 실행안함. Value 로 이미 인식되어서 인듯....
$stmt->execute();

// Fetch 모드를 설정 및 1 row 씩 가져오기
while($row= $stmt->fetch(PDO::FETCH_BOTH)) {
    echo $row[0].' | ';
    echo $row[1].' | ';
    echo $row[2].' | ';
    echo $row[3].'<br />';
}
$dbconn = null; // DB접속 종료
?>

<?php
require_once 'pdoconn.php';
// SELECT
$ItemName ='사과';
$sql="select uid,ItemName,Price,Quantity from table_items where ItemName=:ItemName";
$stmt = $dbconn->prepare($sql);
$stmt->bindParam(':ItemName', $ItemName); // 변수에 바인딩하기 위해 bindParam을 사용
$ItemName ='복숭아';
$stmt->execute();

// Fetch 모드를 설정 및 1 row 씩 가져오기
while($row= $stmt->fetch(PDO::FETCH_BOTH)) {
    echo $row[0].' | ';
    echo $row[1].' | ';
    echo $row[2].' | ';
    echo $row[3].'<br />';
}
$dbconn = null;
?>

 

이렇게 실행해야 하는 경우가 있는지는 모르지만 결과는 두개를 반환한다.

<?php
require_once 'pdoconn.php';
// SELECT
$sql="select uid,ItemName,Price,Quantity from table_items where ItemName=:ItemName";
$stmt = $dbconn->prepare($sql);
$stmt->bindParam(':ItemName', $ItemName); // 변수에 바인딩하기 위해 bindParam을 사용
$ItemNames = array('사과','복숭아');
foreach($ItemNames as $ItemName){
    $stmt->execute();
    // Fetch 모드를 설정 및 1 row 씩 가져오기
    while($row= $stmt->fetch(PDO::FETCH_BOTH)) {
        echo $row[0].' | ';
        echo $row[1].' | ';
        echo $row[2].' | ';
        echo $row[3].'<br />';
    }
}
$dbconn = null;
?>

 

IN 으로 받은 걸 처리하는게 올바른 것인지 궁금하여 Stakoverflow 에서 검색하고 테스트 해본 결과를 적어둔다.

<?php
require_once 'pdoconn.php';
// SELECT
$ItemNames = array('사과','복숭아');
$total_items = count($ItemNames);
$question_marks = array_fill(0, $total_items, '?');
$sql='select uid,ItemName,Price,Quantity from table_items where ItemName IN ('.implode(',', $question_marks). ')';
$stmt = $dbconn->prepare($sql);
$stmt->execute(array_values($ItemNames));

// Fetch 모드를 설정 및 1 row 씩 가져오기
while($row= $stmt->fetch(PDO::FETCH_BOTH)) {
    echo $row[0].' | ';
    echo $row[1].' | ';
    echo $row[2].' | ';
    echo $row[3].'<br />';
}
$dbconn = null;
?>

 

이제 배우는 중이라 그런지 MySQLi 에 비해 불편해보이는 것도 있는거 같다.

익숙해지면 장점이 보일지도.....

 

LIKE 검색어 예제

$likeString = '%' . $string . '%';
 $stmt = $dbconn->prepare("SELECT * FROM tableName WHERE columnName LIKE ?");
 $stmt->bind_param('s', $likeString);
 $stmt->execute();

 

 

Insert 예제

 $stmt = $dbconn->prepare("INSERT INTO db_fruit (id, type, colour) VALUES (:id, :name, :color)");
 $stmt->execute(array('id' => $newId, 'name' => $name, 'color' => $color));

 $stmt = $dbconn->prepare("INSERT INTO db_fruit (id, type, colour) VALUES (? ,? ,?)");
 $stmt->execute(array($newId, $name, $color));

 $stmt = $dbconn->prepare("INSERT INTO db_fruit (`id`, `type`, `colour`) VALUES (:id, :name, :colour)");
 $stmt->bindParam(':id', $newId, PDO::PARAM_INT);
 $stmt->bindParam(':type', $type, PDO::PARAM_INT);
 $stmt->bindParam(':colour', $colour, PDO::PARAM_STR);
 $stmt->execute();

 

https://www.w3schools.com/php/php_mysql_prepared_statements.asp 게시글도 읽어보니 도움 많이 된다.

 

블로그 이미지

Link2Me

,
728x90

Feature differences between mysqli and PDO:

Feature PDO MySQLi
Database support 12 different drivers MySQL and MariaDB
Interface OOP OOP and procedural
Named parameters Yes No
Object mapping Yes Yes
Prepared statements Yes Yes
Non-blocking, asynchronous queries with mysqlnd support No Yes
Multiple Statements support Most Yes
MySQL 5.1+ functionality support Most All

 

MySQL PDO 연결을 시도했더니 에러가 발생한다. 확인해보니 PDO 설치가 되어 있지 않다.

 

 

# 설치여부 확인
/usr/local/php/bin/php -i | grep PDO

 

# 설치한 PHP와 동일한 버전의 소스 다운로드 후 압축해제
# 소스파일에서 설치할 확장모듈과 동일한 이름의 경로로 이동
cd /usr/local/APM/php-5.6.30/ext/pdo_mysql
# phpize 파일을 실행한다.
/usr/local/php/bin/phpize
# configure 및 compile
./configure --with-php-config=/usr/local/php/bin/php-config --with-pdo-mysql=/usr/local/mysql
make
# make 명령어를 실행후 cd modules/ 하고 ll 하여 pdo_mysql.so 파일이 생성되었는지 확인한다.
cd ..
cp -arp modules/pdo_mysql.so /usr/local/php/lib/php/extensions/no-debug-zts-20131226
cd /usr/local/php/lib/php/extensions/no-debug-zts-20131226

# php.ini 파일을 열어 extension=확장모듈.so 파일을 추가한다.
extension=/usr/local/php/lib/php/extensions/no-debug-zts-20131226/pdo_mysql.so

# 아파치(Apache) 재시작한다.
/usr/local/apache/bin/apachectl restart

파일이 설치되었는지 확인한다.
확인방법은
/usr/local/php/bin/php -i | grep PDO
또는 phpinfo()함수로 확인한다.

 

 

블로그 이미지

Link2Me

,