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操作一小部分做了探究,发现我们在特别方便地使用框架时,同时不考虑安全问题的时候,大多数情况人家都为我们考虑好了,所以饮水思源,提升代码能力最好的办法就是看源码了。
对于该用哪个框架,我并不做出回应,看大家个人喜好,要是有时间自己写个最适合自己的框架(其实是从别人的框架上改)也是不错的~~
参考文献
- https://segmentfault.com/a/1190000003722959
- http://www.111cn.net/phper/php-cy/75707.htm
- http://www.php186.com/content/article/php/24120.html
- http://www.jb51.net/article/54713.htm
thinkphp 和 laravel使用sql语句操作db和源码浅析的更多相关文章
- 在myeclipse中配置DB Driver(数据库用MySql),并在myeclipse执行sql语句操作
在myeclipse中配置DB Driver(数据库用MySql),并在myeclipse执行sql语句操作 MyEclipse6.5 , mysq驱动jar包为mysql-connector ...
- Django中使用mysql数据库并使用原生sql语句操作
Django自身默认使用sqlite3这个轻量级的数据库,但是当我们开发网站时,sqlite3就没有mysql好,sqlite3适合一些手机上开发使用的数据库. 准备的软件mysql数据库,版本5.7 ...
- laravel查看sql语句
我自己是用第一种方法来调试的,第三种不行 不知道为啥 laravel查看sql语句 方法一: 我们有时候想测试一段代码生产的 SQL 语句,比如: 我们想看 App\User::all(); 产生的 ...
- 查看 Laravel 的 SQL 语句的方法
在使用 Laravel 的 Eloquent 进行数据查询的时候,很多小伙伴都想看到背后执行的 SQL 语句到底是什么样的,这小笔录就是解决这个小问题的: 在 Providers/AppService ...
- Android中SQLite数据库操作(1)——使用SQL语句操作SQLite数据库
下面是最原始的方法,用SQL语句操作数据库.后面的"Android中SQLite数据库操作(2)--SQLiteOpenHelper类"将介绍一种常用的android封装操作SQL ...
- 043:Django使用原生SQL语句操作数据库
Django使用原生SQL语句操作数据库 Django配置连接数据库: 在操作数据库之前,首先先要连接数据库.这里我们以配置 MySQL 为例来讲解. Django 连接数据库,不需要单独的创建一个连 ...
- SQL语句操作大全
SQL语句操作大全 本文分为以下六个部分: 基础部分 提升部分 技巧部分 数据开发–经典部分 SQL Server基本函数部分 常识部分 一.基础 1.说明:创建数据库CREATE DATABAS ...
- Shell脚本中执行sql语句操作mysql的5种方法【转】
对于自动化运维,诸如备份恢复之类的,DBA经常需要将SQL语句封装到shell脚本.本文描述了在Linux环境下mysql数据库中,shell脚本下调用sql语句的几种方法,供大家参考.对于脚本输出的 ...
- SQL语句操作全集
SQL语句操作全集 下列语句部分是MySQL语句 SQL分类: DDL—数据定义语言(CREATE,ALTER,DROP,DECLARE) DML—数据操纵语言(SELECT,DELETE,UPDAT ...
随机推荐
- 在centos7中使用supermin制作centos6.5docker镜像
原文 按照原文操作发现,版本并非是我们想要的,而是跟宿主机版本一致.不过可以到dockerhub上pull一个centos6.5的镜像 要安装使用docker 需要内核3.10以上,所以在虚拟机中安装 ...
- 数据库字段值为null利用setInc方法无法直接写入
1.数据库字段值为null利用setInc方法无法直接写入,先判断是否为空,再写入. if($points->add($dataList)){ $user=M('cuser'); $null=$ ...
- css-flex布局知识梳理
一.传统的布局 布局的传统解决方案,基于盒装模型,依赖 display 属性 + position属性 + float属性.它对于那些特殊布局非常不方便,比如,垂直居中就不容易实现. 二. Flex的 ...
- inline-block和float的区别,什么时候使用
文章转载于新浪博客http://blog.sina.com.cn/s/blog_5f39af320101qckt.html 只用于学习交流 什么时候使用inline-block,什么时候使用float ...
- CSS清浮动方法总结
浮动----会使当前标签产生上浮效果,从而导致父标签高度塌陷的问题 1. 给父元素指定高度 <div style="height:200px"> <div sty ...
- CentOS 7运维管理笔记(7)----Apache 基于端口的虚拟主机配置
如果一台服务器只有一个IP或需要通过不同的端口访问不同的虚拟主机,可以使用基于端口的虚拟主机配置. (1) 在虚拟机的CentOS7服务器上配置 eth0:4 为192.168.1.214: (2) ...
- 系统变量 %USERPROFILE%
可以用CMD的SET命令来查看现有的系统变量,“="前的部分用%括起来就是. 常用: %USERPROFILE% =C:\Users\用户名 %SystemRoot% =C:\WINDO ...
- Ganglia安装
一.rrdtool安装 1.1 安装依赖包 由于rrdtool依赖的包比较多,而且包之间也存在依赖,故使用yum安装由于服务器无法联网,故使用iso文件创建本地yum源,方法见下: (1)创建iso存 ...
- java.lang.IllegalStateException: Could not load TestContextBootstrapper [null]. Specify @BootstrapWith's 'value' attribute or make the default bootstrapper class available.
1.前几天搭建单元测后,今天用其测试,结果报了这个问题.网上搜索后,刚开始以为原因是 Spring的 依赖版本的问题,我现在的依赖是: 因为其他的比如说 spring-content spring ...
- android webview 播放 video经验总结
在目前PC浏览器上,对video的支持基本都没什么问题了.但是如果用webview去跑这样的页面就会遇到许多问题. 下面一段html <!DOCTYPE html> <html> ...