thinkphp 和 laravel使用sql語句操作db和源碼淺析

前言

對于一個PHP應(yīng)用,可能最多的就是操作數(shù)據(jù)赐写,以致于初學(xué)者有時只把php當(dāng)做數(shù)據(jù)庫增刪查改的工具(這也無可厚非)鸟蜡。而基于框架的語言,在框架中自然不能少了對數(shù)據(jù)庫操作的封裝血淌,總想打開源碼矩欠,看看到底是怎么工作的财剖,趁著在期末考試前有時間~~

thinkphp

首先是這個中國人用的最多的框架說起悠夯。
ps:我是基于thinkphp3.2來說,tp5.x黨見諒~

thinkphp支持對原生的sql語句執(zhí)行躺坟,如:

        $db=M();
        $condition="XXX";
        $sql="select student.id from student where `s_name`= '$condition'";
        $result=$db->query($sql);

既是使用M()方法實例一個空值沦补,然后->query($sql),我們來看下tp的db類的源碼(ThinkPHP\Library\Think\Db):

  public function query($str,$fetchSql=false) {
        $this->initConnect(false);
        if ( !$this->_linkID ) return false;
        $this->queryStr     =   $str;
        if(!empty($this->bind)){
            $that   =   $this;
            $this->queryStr =   strtr($this->queryStr,array_map(function($val) use($that)
                                            {
                                                return '\''.$that->escapeString($val).'\'';
                                            }
                                                 ,$this->bind));
        }
        if($fetchSql){
            return $this->queryStr;
        }
        //釋放前次的查詢結(jié)果
        if ( !empty($this->PDOStatement) ) $this->free();
        $this->queryTimes++;
        N('db_query',1); // 兼容代碼
        // 調(diào)試開始
        $this->debug(true);
        $this->PDOStatement = $this->_linkID->prepare($str);
        if(false === $this->PDOStatement){
            $this->error();
            return false;
        }  
print_r($this->bind);//test
        foreach ($this->bind as $key => $val) {
            if(is_array($val)){
                $this->PDOStatement->bindValue($key, $val[0], $val[1]);
            }else{
                $this->PDOStatement->bindValue($key, $val);
            }
        }
        $this->bind =   array();
        $result =   $this->PDOStatement->execute();
        // 調(diào)試結(jié)束
        $this->debug(false);
        if ( false === $result ) {
            $this->error();
            return false;
        } else {
            return $this->getResult();
        }
    }

可以看到,這個function要是沒有綁定參數(shù)就先是把$str參數(shù)給到一個閉包函數(shù)里進行替換咪橙,$that->escapeString($val)夕膀;

 public function escapeString($str) {
        return addslashes($str);
    }

而這個函數(shù)也只是做了個簡單addslashes($str),連個mysql_real_escape_string()都沒有~~~;還是有sql注入可能性美侦,而我們要是以直接建立sql語句查詢的話产舞,是沒有使用參數(shù)綁定的操作的.然后我們看到$this->PDOStatement->execute(),就是說$this->query();還是進行了pdo 的prepare和execute的操作(雖然只是對其addslashes一下)菠剩,而對于不時select的sql呢易猫,有個$db->execute(),我們接著看源碼。不時很清楚addslashes的點開看看

////////其他都和query()一樣
 if ( false === $result) {
            $this->error();
            return false;
        } else {
            $this->numRows = $this->PDOStatement->rowCount();
            if(preg_match("/^\s*(INSERT\s+INTO|REPLACE\s+INTO)\s+/i", $str)) {
                $this->lastInsID = $this->_linkID->lastInsertId();
            }
            return $this->numRows;
        }
    }

看到了吧具壮,沒有加任何容錯機制准颓,只是對insert語句返回了lastInsertId(),棺妓,然后這個還要我們自己來$db->getLastInsID()來獲取攘已,不過單純的insert也還好啦,怜跑,想加入事務(wù)機制呢样勃,我們接著往下看源碼:

    public function startTrans() {
        $this->initConnect(true);
        if ( !$this->_linkID ) return false;
        //數(shù)據(jù)rollback 支持
        if ($this->transTimes == 0) {
            $this->_linkID->beginTransaction();
        }
        $this->transTimes++;
        return ;
    }
    public function commit() {
        if ($this->transTimes > 0) {
            $result = $this->_linkID->commit();
            $this->transTimes = 0;
            if(!$result){
                $this->error();
                return false;
            }
        }
        return true;
    }

    /**
     * 事務(wù)回滾
     * @access public
     * @return boolean
     */
    public function rollback() {
        if ($this->transTimes > 0) {
            $result = $this->_linkID->rollback();
            $this->transTimes = 0;
            if(!$result){
                $this->error();
                return false;
            }
        }
        return true;
    }

看到了都是基于PDO的,調(diào)用方法也和pdo類似

$m->startTrans();
$result=$m->where('刪除條件')->delete();
$result2=m2->where('刪除條件')->delete();
if($result && $result2){
$m->commit();//成功則提交
}else{
$m->rollback();//不成功,則回滾
}

可我們用框架不能還是老寫sql呀彤灶,要用人家的方法呀~~

        $con['s_name']=$condition;
        $con['id']=3;
        $con['_logic'] = 'OR';
        $field=array('id','s_name');
        $db2=M('student');
        $result2=$db2->where($con)->field($field)->select();
        print_r($result2);
        $result3=$db2->getFieldBys_name('gbw2','id');
        print_r($result3);

我們先關(guān)注下第一個result的源碼,不過在這之前我們先看thinkphp中的返回結(jié)果模式

  protected $options = array(
        PDO::ATTR_CASE              =>  PDO::CASE_LOWER,
        PDO::ATTR_ERRMODE           =>  PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_ORACLE_NULLS      =>  PDO::NULL_NATURAL,
        PDO::ATTR_STRINGIFY_FETCHES =>  false,
    );

其他的咱們都不怎么care看幼,這里<b>PDO::ATTR_STRINGIFY_FETCHES => false ,</b>是不把取出來的數(shù)字結(jié)果轉(zhuǎn)為字符串還有就是innodb(MyISAM 則不然)把insert默認當(dāng)做事務(wù)來處理,<b>PDO::ATTR_AUTOCOMMIT</b>默認是1幌陕,也就是insert自動執(zhí)行诵姜,所以insert語句的時候記得最好加上commit

好的,言歸正傳

 public function select($options=array()) {
        $this->model  =   $options['model'];
        $this->parseBind(!empty($options['bind'])?$options['bind']:array());
        $sql    = $this->buildSelectSql($options);
        $result   = $this->query($sql,!empty($options['fetch_sql']) ? true : false);
        return $result;
    }

    /**
     * 生成查詢SQL
     * @access public
     * @param array $options 表達式
     * @return string
     */
    public function buildSelectSql($options=array()) {
        if(isset($options['page'])) {
            // 根據(jù)頁數(shù)計算limit
            list($page,$listRows)   =   $options['page'];
            $page    =  $page>0 ? $page : 1;
            $listRows=  $listRows>0 ? $listRows : (is_numeric($options['limit'])?$options['limit']:20);
            $offset  =  $listRows*($page-1);
            $options['limit'] =  $offset.','.$listRows;
        }
        $sql  =   $this->parseSql($this->selectSql,$options);
        return $sql;
    }
/*
*
*中間省略
*/
 public function parseSql($sql,$options=array()){
        $sql   = str_replace(        array('%TABLE%','%DISTINCT%','%FIELD%','%JOIN%','%WHERE%','%GROUP%','%HAVING%','%ORDER%','%LIMIT%','%UNION%','%LOCK%','%COMMENT%','%FORCE%'),
            array(
                $this->parseTable($options['table']),
                $this->parseDistinct(isset($options['distinct'])?$options['distinct']:false),
                $this->parseField(!empty($options['field'])?$options['field']:'*'),
                $this->parseJoin(!empty($options['join'])?$options['join']:''),
                $this->parseWhere(!empty($options['where'])?$options['where']:''),
                $this->parseGroup(!empty($options['group'])?$options['group']:''),
                $this->parseHaving(!empty($options['having'])?$options['having']:''),
                $this->parseOrder(!empty($options['order'])?$options['order']:''),
                $this->parseLimit(!empty($options['limit'])?$options['limit']:''),
                $this->parseUnion(!empty($options['union'])?$options['union']:''),
                $this->parseLock(isset($options['lock'])?$options['lock']:false),
                $this->parseComment(!empty($options['comment'])?$options['comment']:''),
                $this->parseForce(!empty($options['force'])?$options['force']:'')
            ),$sql);
        return $sql;
    }

select()中先是parseBind();其余的也是通過固定格式來構(gòu)造sql語句搏熄,最后還是用query執(zhí)行棚唆,換湯不換藥。

  protected function parseBind($bind){
        $this->bind   =   array_merge($this->bind,$bind);
    }
        //我們手動加上bind函數(shù)
    $con['id']=':id';
        $con['_logic'] = 'OR';
        $field=array('id','s_name');
        $db2=M('student');

        $result2=$db2->where($con)->bind(':id',3)->field($field)->select();

        //再在類中打印這個綁定的參數(shù)發(fā)現(xiàn)有了
        print_r($this->bind);

可是奇怪的是在drive類中心例,我并沒有找到bind() 這個函數(shù)宵凌,而且 <b>'DB_BIND_PARAM' => true</b>這個設(shè)置后,在insert/update/delete中會自動綁定參數(shù)還是相對安全的止后,瞎惫,不過還是手動bind下更放心,踏實~

leveral

我們第二個主角登場了译株,thinkphp要是中國用的最多的框架瓜喇,那么leveral則是世界上用的最多的框架了,哈哈~

首先我們先要找到DB類在哪歉糜,是<b>vendor\laravel\framework\src\Illuminate\Support\Facades</b> 嗎乘寒?
我們打開發(fā)現(xiàn)里面就這個protected static function getFacadeAccessor() { return 'db'; }

好像不對呀,我們再找匪补,真正起作用的是<b>\Illuminate\Database\DatabaseManager</b>打開看看吧好像只有一些reconnect(),disconnect()之類的函數(shù)伞辛,我們常見的select呢,夯缺,我們再找找蚤氏,<b>\Illuminate\Database\Connection.php</b>這回是了。
我們先簡單寫幾個demo吧

$users = DB::table('users')->where('votes', '>', 100)->get();
$result=DB::table('users')->select('name', 'email')->get();
$goodsShow = DB::table('goods')->where([product_id'=>$id,'name'=>$name])->first();
//同樣可以使用原生的
$db->select('select * from user where id in (?,?)', [$id,2]);
$users = DB::table('users')->orderBy('name', 'desc')->groupBy('count')->having('count', '>', 100)->get();
DB::table('users')->where('id', 1)->update(array('votes' => 1));

寫了幾個發(fā)現(xiàn)好像和sql還蠻像的踊兜,而且好接受竿滨,這可能正是lereval的魔力吧,我們來看看源碼~

//先看看Illuminate\Database\Connectors\Connector.php
    protected $options = array(
        PDO::ATTR_CASE => PDO::CASE_NATURAL,
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL,
        PDO::ATTR_STRINGIFY_FETCHES => false,
        PDO::ATTR_EMULATE_PREPARES => false,
    );
//初始化
public function __construct(PDO $pdo, $database = '', $tablePrefix = '', array $config = array())
    {
        $this->pdo = $pdo;
....
//先是DB::table
public function table($table)
    {
        $processor = $this->getPostProcessor();

        $query = new Query\Builder($this, $this->getQueryGrammar(), $processor);

        return $query->from($table);
    }
//select
public function select($query, $bindings = array(), $useReadPdo = true)
    {
        return $this->run($query, $bindings, function($me, $query, $bindings) use ($useReadPdo)
        {
            if ($me->pretending()) return array();

            // For select statements, we'll simply execute the query and return an array
            // of the database result set. Each element in the array will be a single
            // row from the database table, and will either be an array or objects.
            $statement = $this->getPdoForSelect($useReadPdo)->prepare($query);

            $statement->execute($me->prepareBindings($bindings));

            return $statement->fetchAll($me->getFetchMode());
        });
    }
//commit
public function commit()
    {
        if ($this->transactions == 1) $this->pdo->commit();

        --$this->transactions;

        $this->fireConnectionEvent('committed');
    }

可以看出同樣支持commit润文,callback等姐呐,選擇table既是from,這都好理解典蝌,和thinkphp大同小異曙砂。
而著重提出的是select(),也是基于pretend和execute的,并且在參數(shù)綁定上做的顯式表示了骏掀,從使用起來即可看到鸠澈,

對比

sql操作不僅限于select/update/delete/insert/柱告,還加入join,union等更復(fù)雜的關(guān)聯(lián)操作笑陈,以及在case when短句等相關(guān)操作际度。一旦非要用這些,我建議還是原生的sql涵妥,然后直接query()來的痛快乖菱,要不容易出錯不說還容易產(chǎn)生性能問題。

thinkphp是國產(chǎn)的蓬网,可能更適用于大多數(shù)國人的思維吧窒所,我在看源碼上也比較清晰。但其中對于參數(shù)的綁定帆锋,和靈活性以及怎么說吵取,對原生sql用戶的體驗感就差點(還有那個令牌系統(tǒng),畫蛇添足有沒有锯厢。皮官。。)实辑。
而laravel的原則就是優(yōu)雅(就是一個方法打死不超過50行捺氢,然后各種return ,use別的方法徙菠,也是醉的不行)讯沈,其中對安全性和用戶操作及學(xué)習(xí)的體驗性則更高一籌

總結(jié)

本文僅對thinkphp 和 laravel中的db操作一小部分做了探究郁岩,發(fā)現(xiàn)我們在特別方便地使用框架時婿奔,同時不考慮安全問題的時候,大多數(shù)情況人家都為我們考慮好了问慎,所以飲水思源萍摊,提升代碼能力最好的辦法就是看源碼了。
對于該用哪個框架如叼,我并不做出回應(yīng)冰木,看大家個人喜好,要是有時間自己寫個最適合自己的框架(其實是從別人的框架上改)也是不錯的~~

參考文獻

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末笼恰,一起剝皮案震驚了整個濱河市踊沸,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌社证,老刑警劉巖逼龟,帶你破解...
    沈念sama閱讀 212,332評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異追葡,居然都是意外死亡腺律,警方通過查閱死者的電腦和手機奕短,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,508評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來匀钧,“玉大人翎碑,你說我怎么就攤上這事≈梗” “怎么了日杈?”我有些...
    開封第一講書人閱讀 157,812評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長佑刷。 經(jīng)常有香客問我达椰,道長,這世上最難降的妖魔是什么项乒? 我笑而不...
    開封第一講書人閱讀 56,607評論 1 284
  • 正文 為了忘掉前任啰劲,我火速辦了婚禮,結(jié)果婚禮上檀何,老公的妹妹穿的比我還像新娘蝇裤。我一直安慰自己,他們只是感情好频鉴,可當(dāng)我...
    茶點故事閱讀 65,728評論 6 386
  • 文/花漫 我一把揭開白布栓辜。 她就那樣靜靜地躺著,像睡著了一般垛孔。 火紅的嫁衣襯著肌膚如雪藕甩。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,919評論 1 290
  • 那天周荐,我揣著相機與錄音狭莱,去河邊找鬼。 笑死概作,一個胖子當(dāng)著我的面吹牛腋妙,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播讯榕,決...
    沈念sama閱讀 39,071評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼骤素,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了愚屁?” 一聲冷哼從身側(cè)響起济竹,我...
    開封第一講書人閱讀 37,802評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎霎槐,沒想到半個月后送浊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,256評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡栽燕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,576評論 2 327
  • 正文 我和宋清朗相戀三年罕袋,在試婚紗的時候發(fā)現(xiàn)自己被綠了改淑。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,712評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡浴讯,死狀恐怖朵夏,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情榆纽,我是刑警寧澤仰猖,帶...
    沈念sama閱讀 34,389評論 4 332
  • 正文 年R本政府宣布,位于F島的核電站奈籽,受9級特大地震影響饥侵,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜衣屏,卻給世界環(huán)境...
    茶點故事閱讀 40,032評論 3 316
  • 文/蒙蒙 一躏升、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧狼忱,春花似錦膨疏、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至窘俺,卻和暖如春饲帅,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背瘤泪。 一陣腳步聲響...
    開封第一講書人閱讀 32,026評論 1 266
  • 我被黑心中介騙來泰國打工灶泵, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人均芽。 一個月前我還...
    沈念sama閱讀 46,473評論 2 360
  • 正文 我出身青樓单鹿,卻偏偏與公主長得像掀宋,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子劲妙,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,606評論 2 350

推薦閱讀更多精彩內(nèi)容

  • 語 句 功 能 數(shù)據(jù)操作 SELECT——從數(shù)據(jù)庫表中檢索數(shù)據(jù)行和列INSERT——向數(shù)據(jù)庫表添加新數(shù)據(jù)行DELE...
    戰(zhàn)敭閱讀 5,079評論 0 53
  • 什么是SQL數(shù)據(jù)庫: SQL是Structured Query Language(結(jié)構(gòu)化查詢語言)的縮寫儒喊。SQL是...
    西貝巴巴閱讀 1,803評論 0 10
  • Spark SQL, DataFrames and Datasets Guide Overview SQL Dat...
    Joyyx閱讀 8,326評論 0 16
  • 回來又幫了兩天工,終于能歇會了怀愧。這兩天累雖不算太累余赢,就是時間熬人,總感覺時間過的太慢妻柒。在上面,不知不覺就一兩個小時...
    goldfish2017閱讀 252評論 0 0
  • 那年 寒風(fēng)凜凜 你我相遇 相見無語只是寒暄問候 那年 烈日炎炎 你我相識 不見相思相見含羞 那年 春風(fēng)浮動 你我相...
    半殘煙雨憶紅樓閱讀 185評論 0 1