iT邦幫忙

0

[筆記,PHP,MySQL]常用方法封裝

將資料庫常用的方法打包起來~

PHPUnit不太熟,雖然都是綠燈,但在資料庫方面不知道怎麼測XD
聽說用mock? 但mock的存取跟資料庫存取應該無關吧,頂多介面一樣....

直接上code

<?php
/**
 * 輔助MySQLi使用。
 * @author 舜<wewe987001@gmail.com>
 * @version 1.0.0
 */
class MySqlHelper
{
    /*  使用教學

    一.載入方式
        require_once('MySqlHelper.php');

    二.使用方式,直接使用不用宣告與初始化

    1.查詢: 出來的結果自行mysqli_fetch_array
        $query = MySqlHelper::query('...sql...');

    2.查詢2: 應用在巢狀迴圈
        while($row1 = MySqlHelper::fetch_array('任意亂數值', '...sql-1...')){
            while($row2 = MySqlHelper::fetch_array('別跟上面重複就可以了', '...sql-2...')){
                //$row2
            }
        }

    3.插入 : 參數形式 key-value
        MySqlHelper::insert(資料表,參數)
        $result = MySqlHelper::insert('admindata', ['adminame' => '_test', 'adminpass' => 'test1']);

    4.插入 : 參數形式 欄位與值分開
        MySqlHelper::insert(資料表, 欄位陣列, 值陣列)
        $result = MySqlHelper::insert('admindata', ['adminame', 'adminpass'], ['_test', '(select 123)']);
        
    5.插入 : 值為sql(子查詢,需用括號包起來)
        MySqlHelper::insert(資料表, 欄位陣列, 值陣列)
        $result = MySqlHelper::insert('admindata', ['adminame' => '(select 123)', 'adminpass' => '(select 123)']);
    
    6 新增/更新/刪除用法一樣,key-value形式與欄位與值分開形式二選一,更新的參數先資料 後條件

    7.更新 : 參數形式 key-value
        MySqlHelper::update('admindata', ['adminpass' => '__123__'], ['adminame' => $adminame]);
         //無須條件就放空陣列
        MySqlHelper::update('admindata', ['adminpass' => '(adminpass)'], []);

    8.更新 : 參數形式 欄位與值分開
        MySqlHelper::update('admindata', ['adminpass'], ['__123__'], ['adminame'], [$adminame]);
         //無須條件就放空陣列
        MySqlHelper::update('admindata', ['adminpass'], ['(adminpass)'], [], []);


    */
    protected static $connection;
    protected static $host;
    protected static $dbname;
    protected static $user;
    protected static $password;
    protected static $fetchStack = [];
    protected static $fetchStackSql = [];

    public function __construct($host = '', $dbname = '', $user = '', $password = '')
    {
        // 是否沒傳任何參數
        $isEmpty = array_reduce(func_get_args(), function ($v1, $v2) {
            return $v1 && $v2 === '';
        }, true);

        // 若有值就覆蓋並重設
        if (!$isEmpty)
            self::reset($host, $dbname, $user, $password);
    }

    // ==========================
    //  連接資料庫
    // ==========================

    /** 處理MySqli連接字串 */
    public static function connect()
    {
        if (!isset(self::$connection)) {
            // p: 永久連線
            self::$connection = mysqli_connect('p:' . self::$host, self::$user, self::$password, self::$dbname);

            if (isset(self::$connection))
                mysqli_query(self::$connection, "SET NAMES utf8");
        }
        return self::$connection;
    }

    public static function reset($host, $dbname, $user, $password)
    {
        self::$host     = $host ?: self::$host;
        self::$dbname   = $dbname ?: self::$dbname;
        self::$user = $user ?: self::$user;
        self::$password = $password ?: self::$password;

        // 關掉先前連線
        if (isset(self::$connection))
            self::$connection->close(); // 關閉先前連線
        self::$connection = null;

        // 清空結果集
        self::clearFetchStack();

        //重新連線
        self::connect();
    }

    // ==========================
    //  資料集處理
    // ==========================
    /**
     * 清空資料集
     * @param 唯一值,預設空值代表全部清空
     */
    protected static function clearFetchStack($id = '')
    {
        if ($id === '') {
            self::$fetchStack = [];
            self::$fetchStackSql = [];
        } else if (isset(self::$fetchStack[$id])) {
            unset(self::$fetchStack[$id]);
            unset(self::$fetchStackSql[$id]);
        }
    }

    /** 取得資料集
     * @param string $id 資料集的id
     * @param string $sql 驗證用,若sql不同就重設
     */
    protected static function GetFetchStack($id, $sql)
    {
        // 如果有此id 且( 沒給sql 或是 sql一樣),就取舊的
        if (array_key_exists($id, self::$fetchStack) && (empty($sql) || self::$fetchStackSql[$id] == $sql))
            return self::$fetchStack[$id];
        else { // 若有不同就新增
            self::$fetchStack[$id] = self::query($sql);
            self::$fetchStackSql[$id] = $sql;
            return self::$fetchStack[$id];
        }
    }
    // ==========================
    //  查詢/撈資料
    // ==========================

    /** 執行查詢(自行fetch取結果)
     * @param string $sql
     * @param bool $bigData 是否是大資料,預設false
     * @return mysqli_result|true|false 回傳
     */
    public static function query($sql, $bigData = false)
    {
        if (!$bigData)
            $query = mysqli_query(self::connect(), $sql);
        else
            $query = mysqli_query(self::connect(), $sql, MYSQLI_USE_RESULT);

        return $query;
    }

    /** 查詢全部資料
     * @param string $sql
     * @param int $resulttype 預設 MYSQLI_BOTH,建議用 MYSQLI_ASSOC
     * @return array
     */
    public static function queryAll($sql, $resulttype = MYSQLI_BOTH){
        $result = array();
        $rand = rand();
        while ($row = self::fetch_array($rand ,$sql, $resulttype)){
            $result[] = $row;
        }
        return $result;
    }

    /** 依序撈資料,提取結果行作為關聯數組,數字數組或兩者(mysqli_fetch_array)
     * @param string $id 唯一值,隨意給個亂數即可;因應可能應用在巢狀迴圈中,為避免跑錯結果集,所以用$id來分辨誰是誰
     * @param string $sql
     * @param int $resulttype 預設MYSQLI_BOTH,提取結果行作為關聯數組,數字數組或兩者
     * @return array|null
     */
    public static function fetch_array($id, $sql, $resulttype = MYSQLI_BOTH)
    {
        // 取得或新增堆疊
        $query = self::GetFetchStack($id, $sql);

        // 撈資料
        $result = mysqli_fetch_array($query, $resulttype);

        // 如果跑完了就從結果集移除
        if (!$result)
            self::clearFetchStack($id);

        return $result;
    }

    /**依序撈資料,獲取結果行作為關聯數組(mysqli_fetch_assoc)
     * @param string $id 唯一值,隨意給個亂數即可;因應可能應用在巢狀迴圈中,為避免跑錯結果集,所以用$id來分辨誰是誰
     * @param string $sql
     * @return array|null
     */
    public static function fetch_assoc($id, $sql)
    {
        return self::fetch_array($id, $sql, MYSQLI_ASSOC);
    }

    /**依序撈資料,獲取結果行作為枚舉數組(mysqli_fetch_row)
     * @param string 唯一值,隨意給個亂數即可;因應可能應用在巢狀迴圈中,為避免跑錯結果集,所以用$id來分辨誰是誰
     * @param string sql
     * @return array|null
     */
    public static function fetch_row($id, $sql)
    {
        return self::fetch_array($id, $sql, MYSQLI_NUM);
    }

    // ==========================
    //  新增/更新/刪除
    //  1.用法可選擇用key-value 或是 keys + valuies
    //  舉例:
    //      (1)欄位名稱與值配對: MySqlHelper::insert('tableName', ['field1'=>'value1', 'field2'=>'value2', ...])
    //      (2)欄位名稱與值分開: MySqlHelper::delete('tableName', ['field1', 'field2', ...], ['value1', 'value2', ...])
    //  2.更新的用法一樣,先資料 後條件
    //  舉例:
    //      (1)欄位名稱與值配對:MySqlHelper::update('tableName' ,['field1'=>'value1', 'field2'=>'value2'] ,['where3'=>'value3'] )
    //      (2)欄位名稱與值分開:MySqlHelper::update('tableName',['dataField1', 'dataField2'], ['dataValue1', 'dataValue2']',['whereField1', 'whereField2'], ['whereValue1', 'whereValue2'])
    //  3.值若是sql,請用括號包起來
    //  舉例:
    //      MySqlHelper::insert('tableName', ['field1'=>'(select 2)'])
    // ==========================

    /**
     * 新增用法
     * (1)key-value:ex. insert('tableName', ['field1'=>'value1', 'field2'=>'value2'])
     * (2)keys and valuies:ex. insert('tableName', ['field1', 'field2'], ['value1', 'value2'])
     * (3)值若是sql,請用括號包起來 ex. insert('tableName', ['field1'=>'(select 2)'])
     */
    public static function insert($table, array $keys_Or_KeyVelue, array $values = [])
    {

        // 前兩個參數必填
        if (empty($table) || !count($keys_Or_KeyVelue))  return false;
        // 若為keys + valuies模式,參數數量對不上就報錯,對地上就改為key-value形式
        if (count($values) && (count($values) != count($keys_Or_KeyVelue)))  return false;

        // key-value形式拆成 keys+valuies
        if (!count($values)) {
            $values = array_values($keys_Or_KeyVelue);
            $keys_Or_KeyVelue = array_keys($keys_Or_KeyVelue);
        }

        // 處理值的格式
        $values = array_map(function($val){return self::escapeSqlString($val);}, $values);

        // 組合SQL
        $sql = 'insert into ' . $table . '(' . join(',', $keys_Or_KeyVelue) . ')values(' . join(',', $values) . ')';

        return self::query($sql);
    }


    /**
     * 刪除用法
     * (1)key-value:ex. delete('tableName', ['field1'=>'value1', 'field2'=>'value2'])
     * (2)keys and valuies:ex. delete('tableName', ['field1', 'field2'], ['value1', 'value2'])
     * (3)值若是sql,請用括號包起來 ex. delete('tableName', ['field1'=>'(select 2)'])
     */
    public static function delete($table, array $keys_Or_KeyVelue, array $values = [])
    {
        // 前兩個參數必填
        if (empty($table) || !count($keys_Or_KeyVelue))  return false;
        // 若為keys + valuies模式,參數數量對不上就報錯
        if (count($values) && (count($values) != count($keys_Or_KeyVelue)))  return false;

        // 值陣列是空的表示是key-value形式,key-value形式拆成 keys+valuies
        if (!count($values)) {
            $values = array_values($keys_Or_KeyVelue);
            $keys_Or_KeyVelue = array_keys($keys_Or_KeyVelue);
        }
        
        // 處理值的格式
        $values = array_map(function($val){return self::escapeSqlString($val);}, $values);

        // 組合條件SQL
        $where ='';
        foreach ($keys_Or_KeyVelue as $index => $field) {
            $where .= $field . '=' . $values[$index] . ' and ';
        }
        if(count($where)){
            $where = ' where '. substr($where, 0, -4);
        }

        // 組合SQL
        $sql = 'delete from ' . $table .$where;

        return self::query($sql);
    }

    /**
     * 更新用法,值若是sql,請用括號包起來
     * (1)key-value:ex. update('tableName' ,['field1'=>'value1', 'field2'=>'value2'] ,['where3'=>'value3'] )
     * (2)keys and valuies:ex. update('tableName',['dataField1', 'dataField2'], ['dataValue1', 'dataValue2']',['whereField1', 'whereField2'], ['whereValue1', 'whereValue2'])
     * @param string 資料表(包含join)
     * @param array 放置 資料key-value 或 資料欄位名稱
     * @param array 放置 條件key-value 或 資料內容
     * @param array 放置 條件欄位名稱
     * @param array 放置 條件內容
     */
    public static function update($table, array ...$param)
    {
        // 沒給表格 與 陣列數量不對(key-value形式有兩個,欄位內容分開有4個)就離開
        if (empty($table) || count($param) <= 1 || count($param) == 3 || count($param) > 4)  return false;

        // 轉成key-value形式
        if(count($param)==4){
            $data = array_combine($param[0], $param[1]) ?: [];
            $where = array_combine($param[2], $param[3]) ?: [];
        }else{
            $data = &$param[0];
            $where = &$param[1];
        }

        $sql ='update '.$table.' set ';

        //資料處理
        foreach ($data as $key => $value) {
            // 防注入處理
            $sql .= $key . '=' .  self::escapeSqlString($value) .',';
        }
        $sql = substr($sql, 0, -1);

        //條件處理
        if(count($where)){
            $sql .= ' where ';
            foreach ($where as $key => $value) {
                // 防注入處理
                $sql .= $key . '=' .  self::escapeSqlString($value) .'and ';
            }
            $sql = substr($sql, 0, -4);
        }

        return self::query($sql);
    }

    // ==========================
    //  共通
    // ==========================
    /** sql字串處理,mysqli內建的防範注入攻擊 */
    protected static function escapeSqlString($val){
        $val = trim($val); // 排除開頭空白

        // 這幾個開頭的不處理或特殊處理【 ( ' " ` 】
        $first = substr($val, 0, 1);
        switch ($first) {
            case '(': // sql
            case '`': // 被視為資料欄位名稱
                return $val;
            case '"':
            case '\'':
                $val = trim($val, '\'"');
            default: // 防注入
                return "'" . mysqli_real_escape_string(self::connect(), $val) . "'";
                break;
        }
    }
}

PHPUnit

<?php

use PHPUnit\Framework\TestCase;

include_once 'MySqlHelper.php';

class MySqlHelperTest extends TestCase
{
    /** 查詢(自行fetch) */
    public function test_query()
    {
        // assertions
        $query = MySqlHelper::query("select 1 from admindata where adminame='admin'");
        list($result) = mysqli_fetch_row($query);
        $this->assertTrue($result == '1');
    }


    /** 查詢全部資料 */
    public function test_queryAll()
    {
        // assertions
        $query = MySqlHelper::queryAll("select 1 from admindata");
        $this->assertTrue(count($query) == 3);
    }

    /* 巢狀迴圈應用,內外不相干 */
    public function test_fetch_array1()
    {
        $msg = '';
        while ($row1 = MySqlHelper::fetch_array('任意亂數值', 'select adminame from admindata')) {
            $msg .= $row1['adminame'] . ':';
            while ($row2 = MySqlHelper::fetch_assoc('別跟上面重複就可以了', 'select adminpass from admindata')) {
                $msg .= $row2['adminpass'] . ',';
            }
            $msg .= ';';
        }
        $this->assertTrue($msg == 'admin:practice,turrteled,teaching99,;turtle:practice,turrteled,teaching99,;ADM:practice,turrteled,teaching99,;');
    }

    /* 巢狀迴圈應用,內層隨外層影響 */
    public function test_fetch_array2()
    {
        $msg = '';
        while ($row1 = MySqlHelper::fetch_array('任意亂數值', 'select adminame from admindata')) {
            $msg .= $row1['adminame'] . ':';
            while ($row2 = MySqlHelper::fetch_assoc('別跟上面重複就可以了', 'select adminpass from admindata where adminame=\'' . $row1['adminame'] . '\'')) {
                $msg .= $row2['adminpass'] . ',';
            }
            $msg .= ';';
        }
        $this->assertTrue($msg == 'admin:practice,;turtle:turrteled,;ADM:teaching99,;');
    }

    /** 插入驗證
     * (1)驗證key-value形式
     * (2)驗證欄位與值分開測試形式
     * (2)驗證內容是sql(子查詢)
     */
    public function test_insert()
    {
        // assertions
        $table = 'admindata';
        $adminame = '_test';
        $data1 = ['adminame' => $adminame, 'adminpass' => 'test1'];
        $fild2 = ['adminame', 'adminpass'];
        $data2 = [$adminame, 'test2'];
        $testData3 = ['adminame' => $adminame, 'adminpass' => '(select \'test3\')'];

        // 測試一:key-value測試
        $result1 = MySqlHelper::insert($table, $data1);
        $this->assertTrue($result1, 'key-value測試 失敗');

        // 測試一:驗證
        $review1 = current(MySqlHelper::queryAll("select 1 from admindata where adminpass='test1'", MYSQLI_ASSOC));
        $this->assertTrue(current($review1) == '1', 'key-value測試 驗證失敗');

        // 測試二:欄位與值分開測試
        $result2 = MySqlHelper::insert($table, $fild2, $data2);
        $this->assertTrue($result2, '欄位與值分開測試 失敗');

        // 測試二:驗證
        $review2 = current(MySqlHelper::queryAll("select 1 from admindata where adminpass='test2'", MYSQLI_ASSOC));
        $this->assertTrue(current($review2) == '1', '欄位與值分開測試 驗證失敗');

        // 刪掉測試資料
        $query = MySqlHelper::query("delete from admindata where adminame='$adminame'");

        // 測試三:內容是sql(子查詢)
        $result3 = MySqlHelper::insert($table, $testData3);
        $this->assertTrue($result3, '內容是sql(子查詢) 失敗');

        // 測試三:驗證
        $review3 = current(MySqlHelper::queryAll("select adminpass from admindata where adminame='$adminame'", MYSQLI_ASSOC));
        $this->assertTrue(current($review3) == 'test3', '內容是sql(子查詢)測試 驗證失敗');

        // 刪掉測試資料
        // $query = MySqlHelper::query("delete from admindata where adminame='$adminame'");
        $query = MySqlHelper::delete('admindata', ['adminame'], [$adminame]);
    }

    /** 綜合測試1:新增/更新/刪除 key-value形式 */
    public function test_complex1()
    {
        $adminame = '_test';
        $oldPasswd = 'test1';
        $newPasswd = 'AAAAAAAA';

        //新增測試資料
        MySqlHelper::insert('admindata', ['adminame' => $adminame, 'adminpass' => $oldPasswd]);
        
        //驗證
        $review1 = current(MySqlHelper::queryAll("select 1 from admindata where adminame='$adminame' and adminpass='$oldPasswd'", MYSQLI_ASSOC));
        $this->assertTrue(current($review1) == '1', '新增測試資料失敗');

        //更新測試資料
        MySqlHelper::update('admindata', ['adminpass' => $newPasswd], ['adminame' => $adminame]);
        
        //驗證
        $review1 = current(MySqlHelper::queryAll("select 1 from admindata where adminame='$adminame' and adminpass='$newPasswd'", MYSQLI_ASSOC));
        $this->assertTrue(current($review1) == '1', '更新密碼失敗');

        //刪掉測試資料
        MySqlHelper::delete('admindata', ['adminame'=>$adminame,'adminpass'=>$newPasswd]);
        
        //驗證
        $review1 = MySqlHelper::queryAll("select 1 from admindata where adminame='$adminame' and adminpass='$newPasswd'", MYSQLI_ASSOC);
        $this->assertTrue(count($review1) == 0, '刪除測試資料失敗');
    }

    /** 綜合測試2:新增/更新/刪除 欄位內容分開形式 */
    public function test_complex2()
    {
        $adminame = '_test';
        $oldPasswd = 'test1';
        $newPasswd = 'AAAAAAAA';

        //新增測試資料
        MySqlHelper::insert('admindata', ['adminame', 'adminpass'], [$adminame, $oldPasswd]);
        
        //驗證
        $review1 = current(MySqlHelper::queryAll("select 1 from admindata where adminame='$adminame' and adminpass='$oldPasswd'", MYSQLI_ASSOC));
        $this->assertTrue(current($review1) == '1', '新增測試資料失敗');

        //更新測試資料
        MySqlHelper::update('admindata', ['adminpass'], [$newPasswd], ['adminame'], [$adminame]);

        //驗證
        $review1 = current(MySqlHelper::queryAll("select 1 from admindata where adminame='$adminame' and adminpass='$newPasswd'", MYSQLI_ASSOC));
        $this->assertTrue(current($review1) == '1', '更新密碼失敗');

        //刪掉測試資料
        MySqlHelper::delete('admindata', ['adminame','adminpass'], [$adminame,$newPasswd]);

        //驗證
        $review1 = MySqlHelper::queryAll("select 1 from admindata where adminame='$adminame' and adminpass='$newPasswd'", MYSQLI_ASSOC);
        $this->assertTrue(count($review1) == 0, '刪除測試資料失敗');
    }
}


尚未有邦友留言

立即登入留言