TP自从3.2.3开始就在使用PDO方式链接数据库,现在我就研究研究TP5的数据库链接操作 PDO。以及其对数据库操作 的流程。
TP5 默认使用的 是PDO 的方式链接数据库。下面对PDO进行了解释。
百度解释:
1、PDO并不能使用PDO扩展本身执行任何数据库操作,必须使用一个database-specific PDO driver(针对特定数据库的PDO驱动)访问数据库服务器。2、PDO并不提供数据库抽象,它并不会重写SQL或提供数据库本身缺失的功能,如果你需要这种功能,你需要使用一个更加成熟的抽象层。
3、PDO需要PHP5核心OO特性的支持,所以它无法运行于之前的PHP版本。
作用:
(1)解决 数据库的注入问题
Notice:① ② …. 符号代表的是其他对应解释 .
TP5和以前一样可以通过定义全局配置文件 database.php
来辅助Db类库的调用。
在普通的控制器类中调用Db,根据看到的辅助函数db()
找到
操作数据有两种方式
Db::query("select * from think_user where status=1");
与
Db::contect()->query("select * from think_user where status=1");
//下面的 分析可以告诉你,这两个 调用的都是一个 Query类对象
查看connect 这个方法:
/**
* 数据库初始化 并取得数据库类实例
* @static
* @access public
* @param mixed $config 连接配置
* @param bool|string $name 连接标识 true 强制重新连接
* @return \think\db\Connection
* @throws Exception
*/
public static function connect($config = [], $name = false)
{
if (false === $name) {
$name = md5(serialize($config));
}
if (true === $name || !isset(self::$instance[$name])) {
// 解析连接参数 支持数组和字符串
$options = self::parseConfig($config);
//① 如果没有传入配置参数的话, 会通过$config = Config::get('database');这个来获取配置值。
if (empty($options['type'])) {
throw new \InvalidArgumentException('Underfined db type');
}
$class = false !== strpos($options['type'], '\\') ? $options['type'] : '\\think\\db\\connector\\' . ucwords($options['type']);
// 记录初始化信息
if (App::$debug) {
Log::record('[ DB ] INIT ' . $options['type'], 'info');
//哇咔咔,写入日志文件,可以看看方式,后面可能自己也用的上。
}
//② $class 打印出来为 \think\db\connector\Mysql ,在下面进行了实例化
if (true === $name) {
return new $class($options);
} else {
self::$instance[$name] = new $class($options);
}
}
return self::$instance[$name];
}
结果:这里会去 实例化 \think\db\connector\Mysql('配置')
类
现在开始实例化 thinkphp\library\think\db\connector\Mysql.php
文件 的 Mysql 类,这个类实现了 连接器 connection 抽象类 ,即 thinkphp\library\think\db\Connection.php
文件。
Connection 这个抽象类,和 其子类 Mysql , Pgsql, … 等,构成了一个 类似 适配器 的设计模式。
通过 Db::connect($config, $force)->name($name);
方式最终操作的是 \think\db\Query 这个类 。文件是:thinkphp\library\think\db\Query.php
分析
//在 连接器 connection 类中,有下面的魔术方法 call
/**
* 调用Query类的查询方法
* @access public
* @param string $method 方法名称
* @param array $args 调用参数
* @return mixed
*/
public function __call($method, $args)
{
if (!isset($this->query['database'])) {
$class = $this->config['query'];
$this->query['database'] = new $class($this);
}
//这里的 $calss 就是 \think\db\Query ; $model 就是 name
return call_user_func_array([$this->query['database'], $method], $args);
}
查看 Query 类 的 name 方法
/**
* 指定默认的数据表名(不含前缀)
* @access public
* @param string $name
* @return $this
*/
public function name($name)
{
$this->name = $name;
return $this;
}
跟踪这个thinkphp/library/think/Db.php
类,找到了下面这个
// 调用驱动类的方法
public static function __callStatic($method, $params)
{
// 自动初始化数据库 -- 条用了回调
return call_user_func_array([self::connect(), $method], $params);
}
//__callStatic 是PHP5.3后添加的新的魔术方法
//__callStatic 当调用的静态方法不存在或权限不足时,会自动调用__callStatic方法。
这玩意 和最上面 对象成员访问符 操作的是一个 类。
所以才有这样的访问 方式:
Db::query(“select * from think_user where status=1”);
与
Db::contect()->query(”select * from think_user where status=1”);
功能完全相同
到这里 最开始的 那段 $model 所代表的代码就结束了。可惜好像 并有发现,和PDO有什么联系。都到这里了,任然没有链接数据库,没有任何错误报出。对于这种情况,TP官方给出的解释是:thinkphp的数据库链接是惰性的,只有在使用的时候才会去链接。
根据 query 方法来探索。
/**
* 执行查询 返回数据集
* @access public
* @param string $sql sql指令
* @param array $bind 参数绑定
* @param boolean $master 是否在主服务器读操作
* @param bool|string $class 指定返回的数据集对象
* @return mixed
* @throws BindParamException
* @throws PDOException
*/
public function query($sql, $bind = [], $master = false, $class = false)
{
return $this->connection->query($sql, $bind, $master, $class);
}
//这里又 调用回 Connection 连接器 的 query 方法去了
Connection 链接器 的query 方法
/**
* 执行查询 返回数据集
* @access public
* @param string $sql sql指令
* @param array $bind 参数绑定
* @param boolean $master 是否在主服务器读操作
* @param bool|string $class 指定返回的数据集对象
* @return mixed
* @throws BindParamException
* @throws PDOException
*/
public function query($sql, $bind = [], $master = false, $class = false)
{
$this->initConnect($master); // ㈠ 初始化链接数据库
if (!$this->linkID) {
return false;
}
// 根据参数绑定组装最终的SQL语句
$this->queryStr = $this->getRealSql($sql, $bind);
//释放前次的查询结果
if (!empty($this->PDOStatement)) {
$this->free();
}
Db::$queryTimes++;
try {
// 调试开始
$this->debug(true);
// 预处理
$this->PDOStatement = $this->linkID->prepare($sql);
// 参数绑定
$this->bindValue($bind);
// 执行查询
$result = $this->PDOStatement->execute();
// 调试结束
$this->debug(false);
$procedure = in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']);
return $this->getResult($class, $procedure);
} catch (\PDOException $e) {
throw new PDOException($e, $this->config, $this->queryStr);
}
}
㈠ 初始化数据库链接
这里调用了 了Connection 连接器的 connect() 链接数据库的方法。
/**
* 连接数据库方法
* @access public
* @param array $config 连接参数
* @param integer $linkNum 连接序号
* @param array|bool $autoConnection 是否自动连接主数据库(用于分布式)
* @return PDO
* @throws Exception
*/
public function connect(array $config = [], $linkNum = 0, $autoConnection = false)
{
if (!isset($this->links[$linkNum])) {
if (!$config) {
$config = $this->config;
} else {
$config = array_merge($this->config, $config);
}
// 连接参数
if (isset($config['params']) && is_array($config['params'])) {
$params = $config['params'] + $this->params;
} else {
$params = $this->params;
}
// 记录当前字段属性大小写设置
$this->attrCase = $params[PDO::ATTR_CASE];
// 记录数据集返回类型
if (isset($config['resultset_type'])) {
$this->resultSetType = $config['resultset_type'];
}
try {
if (empty($config['dsn'])) {
$config['dsn'] = $this->parseDsn($config);
}
if ($config['debug']) {
$startTime = microtime(true);
}
//PDO 连接了
$this->links[$linkNum] = new PDO($config['dsn'], $config['username'], $config['password'], $params);
if ($config['debug']) {
// 记录数据库连接信息
Log::record('[ DB ] CONNECT:[ UseTime:' . number_format(microtime(true) - $startTime, 6) . 's ] ' . $config['dsn'], 'sql');
}
} catch (\PDOException $e) {
if ($autoConnection) {
Log::record($e->getMessage(), 'error');
return $this->connect($autoConnection, $linkNum);
} else {
throw $e;
}
}
}
return $this->links[$linkNum];
}
在上面找到 这句:
$this->links[$linkNum] = new PDO($config['dsn'], $config['username'], $config['password'], $params);
//这里就是 实例化 PDO 链接了。
到这里 PDO 的流程算是分析完毕了。
② 查看 thinkphp/library/think/Config.php
文件
//可以看到 在查看配置问价事,直接调用的是 静态属性 $config 里面的值,也就是说,在链接数据库之前,系统已经将配置信息加载到了静态属性 $config 。
//放在代码区的静态属性,不会因为对象完成而释放掉,值是不会丢失的。
/**
* 获取配置参数 为空则获取所有配置
* @param string $name 配置参数名(支持二级配置 .号分割)
* @param string $range 作用域
* @return mixed
*/
public static function get($name = null, $range = '')
{
$range = $range ?: self::$range;
// 无参数时获取所有
if (empty($name) && isset(self::$config[$range])) {
return self::$config[$range];
}
if (!strpos($name, '.')) {
$name = strtolower($name);
var_dump(self::$config[$range][$name]);
return isset(self::$config[$range][$name]) ? self::$config[$range][$name] : null;
} else {
// 二维数组设置和获取支持
$name = explode('.', $name);
$name[0] = strtolower($name[0]);
return isset(self::$config[$range][$name[0]][$name[1]]) ? self::$config[$range][$name[0]][$name[1]] : null;
}
}
Db::table(xxx)
没有报错,应该尚未链接数据库。 这里仅仅是 实例化了 链接类。