thinkphp 和 laravel使用sql语句操作db和源码浅析

thinkphp 和 laravel是phper开发中用的比较多的两个框架,无所谓好坏,看个人习惯及喜爱!
1
前言对于一个PHP应用,可能最多的就是操作数据,以致于初学者有时只把php当做数据库增删查改的工具(这也无可厚非)。而基于框架的语言,在框架中自然不能少了对数据库操作的封装,总想打开源码,看看到底是怎么工作的,趁着有时间~~

thinkphp【tp框架】

 

1
2
3
4
首先是这个中国人用的最多的框架说起。
ps:我是基于thinkphp3.2来说,tp5.x党见谅~
 
thinkphp支持对原生的sql语句执行,如:
1
2
3
4
$db =M();
       $condition = "XXX" ;
       $sql = "select student.id from student where `s_name`= '$condition'" ;
       $result = $db ->query( $sql );
1
既是使用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();
        }
    }
1
可以看到,这个 function 要是没有绑定参数就先是把 $str 参数给到一个闭包函数里进行替换, $that ->escapeString( $val );
 public function escapeString($str) {
        return addslashes($str);
    }
1
而这个函数也只是做了个简单 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;
        }
    }
1
看到了吧,没有加任何容错机制,只是对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;
    }
1
看到了都是基于PDO的,调用方法也和pdo类似
$m->startTrans();
$result=$m->where('删除条件')->delete();
$result2=m2->where('删除条件')->delete();
if($result && $result2){
$m->commit();//成功则提交
}else{
$m->rollback();//不成功,则回滚
}
1
<br>可我们用框架不能还是老写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);
1
我们先关注下第一个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,
    );
1
2
其他的咱们都不怎么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;
    }
1
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

1
2
3
4
5
6
7
我们第二个主角登场了,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吧

 

1
2
3
4
5
6
7
$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)); 
1
写了几个发现好像和sql还蛮像的,而且好接受,这可能正是lereval的魔力吧,我们来看看源码~
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
//先看看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' );
     } 
1
2
可以看出同样支持commit,callback等,选择table既是from,这都好理解,和thinkphp大同小异。
而着重提出的是select(),也是基于pretend和execute的,并且在参数绑定上做的显式表示了,从使用起来即可看到,

对比

1
2
3
4
sql操作不仅限于select/update/ delete /insert/,还加入join,union等更复杂的关联操作,以及在 case when短句等相关操作。一旦非要用这些,我建议还是原生的sql,然后直接query()来的痛快,要不容易出错不说还容易产生性能问题。
 
thinkphp是国产的,可能更适用于大多数国人的思维吧,我在看源码上也比较清晰。但其中对于参数的绑定,和灵活性以及怎么说,对原生sql用户的体验感就差点(还有那个令牌系统,画蛇添足有没有。。。)。
而laravel的原则就是优雅(就是一个方法打死不超过50行,然后各种 return use 别的方法,也是醉的不行),其中对安全性和用户操作及学习的体验性则更高一筹

 

总结

1
2
本文仅对thinkphp 和 laravel中的db操作一小部分做了探究,发现我们在特别方便地使用框架时,同时不考虑安全问题的时候,大多数情况人家都为我们考虑好了,所以饮水思源,提升代码能力最好的办法就是看源码了。
对于该用哪个框架,我并不做出回应,看大家个人喜好,要是有时间自己写个最适合自己的框架(其实是从别人的框架上改)也是不错的~~
参考文献
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值