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

thinkphp 和 laravel是phper開發中用的比較多的兩個框架,無所謂好壞,看我的習慣及喜好!
前言對於一個PHP應用,可能最多的就是操做數據,以至於初學者有時只把php當作數據庫增刪查改的工具(這也無可厚非)。而基於框架的語言,在框架中天然不能少了對數據庫操做的封裝,總想打開源碼,看看究竟是怎麼工做的,趁着有時間~~

thinkphp【tp框架】

 

首先是這個中國人用的最多的框架提及。
ps:我是基於thinkphp3.2來講,tp5.x黨見諒~

thinkphp支持對原生的sql語句執行,如:
  $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; } //釋放前次的查詢結果 if ( !empty($this->PDOStatement) ) $this->free(); $this->queryTimes++; N('db_query',1); // 兼容代碼 // 調試開始 $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(); // 調試結束 $this->debug(false); if ( false === $result ) { $this->error(); return false; } else { return $this->getResult(); } }
 
能夠看到,這個function要是沒有綁定參數就先是把$str參數給到一個閉包函數裏進行替換,$that->escapeString($val);
public function escapeString($str) { return addslashes($str); }
 
而這個函數也只是作了個簡單addslashes($str),連個mysql_real_escape_string()都沒有~~~;仍是有sql注入可能性,而咱們要是以直接創建sql語句查詢的話,是沒有使用參數綁定的操做的.而後咱們看到$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也還好啦,,想加入事務機制呢,咱們接着往下看源碼:
public function startTrans() { $this->initConnect(true); if ( !$this->_linkID ) return false; //數據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; } /** * 事務回滾 * @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的,調用方法也和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);
 
 
咱們先關注下第一個result的源碼,不過在這以前咱們先看thinkphp中的返回結果模式
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,這裏PDO::ATTR_STRINGIFY_FETCHES => false ,是不把取出來的數字結果轉爲字符串還有就是innodb(MyISAM 則否則)把insert默認當作事務來處理,PDO::ATTR_AUTOCOMMIT默認是1,也就是insert自動執行,因此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'])) { // 根據頁數計算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();其他的也是經過固定格式來構造sql語句,最後仍是用query執行,換湯不換藥。
protected function parseBind($bind){ $this->bind = array_merge($this->bind,$bind); }
//咱們手動加上bind函數 $con['id']=':id'; $con['_logic'] = 'OR'; $field=array('id','s_name'); $db2=M('student'); $result2=$db2->where($con)->bind(':id',3)->field($field)->select(); //再在類中打印這個綁定的參數發現有了 print_r($this->bind);
但是奇怪的是在drive類中,我並無找到bind() 這個函數,並且'DB_BIND_PARAM' => true這個設置後,在insert/update/delete中會自動綁定參數仍是相對安全的,,不過仍是手動bind下更放心,踏實~

leveral

咱們第二個主角登場了,thinkphp要是中國用的最多的框架,那麼leveral則是世界上用的最多的框架了,哈哈~

首先咱們先要找到DB類在哪,是vendor\laravel\framework\src\Illuminate\Support\Facades 嗎?
咱們打開發現裏面就這個protected static function getFacadeAccessor() { return 'db'; }

好像不對呀,咱們再找,真正起做用的是\Illuminate\Database\DatabaseManager打開看看吧好像只有一些reconnect(),disconnect()之類的函數,咱們常見的select呢,,咱們再找找,\Illuminate\Database\Connection.php這回是了。
咱們先簡單寫幾個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)); 
寫了幾個發現好像和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的,而且在參數綁定上作的顯式表示了,從使用起來便可看到,

對比

sql操做不只限於select/update/delete/insert/,還加入join,union等更復雜的關聯操做,以及在case when短句等相關操做。一旦非要用這些,我建議仍是原生的sql,而後直接query()來的痛快,要不容易出錯不說還容易產生性能問題。

thinkphp是國產的,可能更適用於大多數國人的思惟吧,我在看源碼上也比較清晰。但其中對於參數的綁定,和靈活性以及怎麼說,對原生sql用戶的體驗感就差點(還有那個令牌系統,多此一舉有沒有。。。)。
而laravel的原則就是優雅(就是一個方法打死不超過50行,而後各類return ,use別的方法,也是醉的不行),其中對安全性和用戶操做及學習的體驗性則更高一籌

 

總結

本文僅對thinkphp 和 laravel中的db操做一小部分作了探究,發現咱們在特別方便地使用框架時,同時不考慮安全問題的時候,大多數狀況人家都爲咱們考慮好了,因此飲水思源,提高代碼能力最好的辦法就是看源碼了。
對於該用哪一個框架,我並不作出迴應,看你們我的喜愛,要是有時間本身寫個最適合本身的框架(實際上是從別人的框架上改)也是不錯的~~

參考文獻

相關文章
相關標籤/搜索