php - Laravel核心解读--Database(一)基础介绍 - 网管叨bi叨 - SegmentFault 思否


本站和网页 https://segmentfault.com/a/1190000014220442 的作者无关,不对其内容负责。快照谨为网络故障时之索引,不代表被搜索网站的即时页面。

php - Laravel核心解读--Database(一)基础介绍 - 网管叨bi叨 - SegmentFault 思否
注册登录问答专栏标签招聘活动发现✓使用“Bing”搜本站使用“Google”搜本站使用“百度”搜本站站内搜索注册登录Laravel核心解读--Database(一)基础介绍Kevin5k89关注作者首页专栏网管叨bi叨文章详情4Laravel核心解读--Database(一)基础介绍Kevin5k89发布于2018-04-07  
在我们学习和使用一个开发框架时,无论使用什么框架,如何连接数据库、对数据库进行增删改查都是学习的重点,在Laravel中我们可以通过两种方式与数据库进行交互:
DB, DB是与PHP底层的PDO直接进行交互的,通过查询构建器提供了一个方便的接口来创建及运行数据库查询语句。
Eloquent Model, Eloquent是建立在DB的查询构建器基础之上,对数据库进行了抽象的ORM,功能十分丰富让我们可以避免写复杂的SQL语句,并用优雅的方式解决了数据表之间的关联关系。
上面说的这两个部分都包括在了Illuminate/Database包里面,除了作为Laravel的数据库层Illuminate/Database还是一个PHP数据库工具集, 在任何项目里你都可以通过composer install illuminate/databse安装并使用它。
Database服务注册和初始化
Database也是作为一种服务注册到服务容器里提供给Laravel应用使用的,它的服务提供器是Illuminate\Database\DatabaseServiceProvider
public function register()
Model::clearBootedModels();
$this->registerConnectionServices();
$this->registerEloquentFactory();
$this->registerQueueableEntityResolver();
第一步:Model::clearBootedModels()。在 Eloquent 服务启动之前为了保险起见需要清理掉已经booted的Model和全局查询作用域
/**
* Clear the list of booted models so they will be re-booted.
* @return void
*/
public static function clearBootedModels()
static::$booted = [];
static::$globalScopes = [];
第二步:注册ConnectionServices
protected function registerConnectionServices()
$this->app->singleton('db.factory', function ($app) {
return new ConnectionFactory($app);
});
$this->app->singleton('db', function ($app) {
return new DatabaseManager($app, $app['db.factory']);
});
$this->app->bind('db.connection', function ($app) {
return $app['db']->connection();
});
db.factory用来创建数据库连接实例,它将被注入到DatabaseManager中,在讲服务容器绑定时就说过了依赖注入的其中一个作用是延迟初始化对象,所以只要在用到数据库连接实例时它们才会被创建。
db DatabaseManger 作为Database面向外部的接口,DB这个Facade就是DatabaseManager的静态代理。应用中所有与Database有关的操作都是通过与这个接口交互来完成的。
db.connection 数据库连接实例,是与底层PDO接口进行交互的底层类,可用于数据库的查询、更新、创建等操作。
所以DatabaseManager作为接口与外部交互,在应用需要时通过ConnectionFactory创建了数据库连接实例,最后执行数据库的增删改查是由数据库连接实例来完成的。
第三步:注册Eloquent工厂
protected function registerEloquentFactory()
$this->app->singleton(FakerGenerator::class, function ($app) {
return FakerFactory::create($app['config']->get('app.faker_locale', 'en_US'));
});
$this->app->singleton(EloquentFactory::class, function ($app) {
return EloquentFactory::construct(
$app->make(FakerGenerator::class), $this->app->databasePath('factories')
);
});
启动数据库服务
public function boot()
Model::setConnectionResolver($this->app['db']);
Model::setEventDispatcher($this->app['events']);
数据库服务的启动主要设置 Eloquent Model 的连接分析器(connection resolver),让model能够用db服务连接数据库。还有就是设置数据库事件的分发器 dispatcher,用于监听数据库的事件。
DatabaseManager
上面说了DatabaseManager是整个数据库服务的接口,我们通过DB门面进行操作的时候实际上调用的就是DatabaseManager,它会通过数据库连接对象工厂(ConnectionFacotry)获得数据库连接对象(Connection),然后数据库连接对象会进行具体的CRUD操作。我们先看一下DatabaseManager的构造函数:
public function __construct($app, ConnectionFactory $factory)
$this->app = $app;
$this->factory = $factory;
ConnectionFactory是在上面介绍的绑定db服务的时候传递给DatabaseManager的。比如我们现在程序里执行了DB::table('users')->get(), 在DatabaseManager里并没有table方法然后就会触发魔术方法__call:
class DatabaseManager implements ConnectionResolverInterface
protected $app;
protected $factory;
protected $connections = [];
public function __call($method, $parameters)
return $this->connection()->$method(...$parameters);
public function connection($name = null)
list($database, $type) = $this->parseConnectionName($name);
$name = $name ?: $database;
if (! isset($this->connections[$name])) {
$this->connections[$name] = $this->configure(
$this->makeConnection($database), $type
);
return $this->connections[$name];
connection方法会返回数据库连接对象,这个过程首先是解析连接名称parseConnectionName
protected function parseConnectionName($name)
$name = $name ?: $this->getDefaultConnection();
// 检查connection name 是否以::read, ::write结尾 比如'ucenter::read'
return Str::endsWith($name, ['::read', '::write'])
? explode('::', $name, 2) : [$name, null];
public function getDefaultConnection()
// laravel默认是mysql,这里假定是常用的mysql连接
return $this->app['config']['database.default'];
如果没有指定连接名称,Laravel会使用database配置里指定的默认连接名称, 接下来makeConnection方法会根据连接名称来创建连接实例:
protected function makeConnection($name)
//假定$name是'mysql', 从config/database.php中获取'connections.mysql'的配置
$config = $this->configuration($name);
//首先去检查在应用启动时是否通过连接名注册了extension(闭包), 如果有则通过extension获得连接实例
//比如在AppServiceProvider里通过DatabaseManager::extend('mysql', function () {...})
if (isset($this->extensions[$name])) {
return call_user_func($this->extensions[$name], $config, $name);
//检查是否为连接配置指定的driver注册了extension, 如果有则通过extension获得连接实例
if (isset($this->extensions[$driver])) {
return call_user_func($this->extensions[$driver], $config, $name);
// 通过ConnectionFactory数据库连接对象工厂获取Mysql的连接类
return $this->factory->make($config, $name);
ConnectionFactory
上面makeConnection方法使用了数据库连接对象工程来获取数据库连接对象,我们来看一下工厂的make方法:
/**
* 根据配置创建一个PDO连接
* @param array $config
* @param string $name
* @return \Illuminate\Database\Connection
*/
public function make(array $config, $name = null)
$config = $this->parseConfig($config, $name);
if (isset($config['read'])) {
return $this->createReadWriteConnection($config);
return $this->createSingleConnection($config);
protected function parseConfig(array $config, $name)
return Arr::add(Arr::add($config, 'prefix', ''), 'name', $name);
在建立连接之前, 先通过parseConfig向配置参数中添加默认的 prefix 属性与 name 属性。
接下来根据配置文件中是否设置了读写分离。如果设置了读写分离,那么就会调用 createReadWriteConnection 函数,生成具有读、写两个功能的 connection;否则的话,就会调用 createSingleConnection 函数,生成普通的连接对象。
protected function createSingleConnection(array $config)
$pdo = $this->createPdoResolver($config);
return $this->createConnection(
$config['driver'], $pdo, $config['database'], $config['prefix'], $config
);
protected function createConnection($driver, $connection, $database, $prefix = '', array $config = [])
......
switch ($driver) {
case 'mysql':
return new MySqlConnection($connection, $database, $prefix, $config);
case 'pgsql':
return new PostgresConnection($connection, $database, $prefix, $config);
......
throw new InvalidArgumentException("Unsupported driver [$driver]");
创建数据库连接的方法createConnection里参数$pdo是一个闭包:
function () use ($config) {
return $this->createConnector($config)->connect($config);
};
这就引出了Database服务中另一部份连接器Connector, Connection对象是依赖连接器连接上数据库的,所以在探究Connection之前我们先来看看连接器Connector。
Connector
在illuminate/database中连接器Connector是专门负责与PDO交互连接数据库的,我们接着上面讲到的闭包参数$pdo往下看
createConnector方法会创建连接器:
public function createConnector(array $config)
if (! isset($config['driver'])) {
throw new InvalidArgumentException('A driver must be specified.');
if ($this->container->bound($key = "db.connector.{$config['driver']}")) {
return $this->container->make($key);
switch ($config['driver']) {
case 'mysql':
return new MySqlConnector;
case 'pgsql':
return new PostgresConnector;
case 'sqlite':
return new SQLiteConnector;
case 'sqlsrv':
return new SqlServerConnector;
throw new InvalidArgumentException("Unsupported driver [{$config['driver']}]");
这里我们还是以mysql举例看一下Mysql的连接器。
class MySqlConnector extends Connector implements ConnectorInterface
public function connect(array $config)
//生成PDO连接数据库时用的DSN连接字符串
$dsn = $this->getDsn($config);
//获取要传给PDO的选项参数
$options = $this->getOptions($config);
//创建一个PDO连接对象
$connection = $this->createConnection($dsn, $config, $options);
if (! empty($config['database'])) {
$connection->exec("use `{$config['database']}`;");
//为连接设置字符集和collation
$this->configureEncoding($connection, $config);
//设置time zone
$this->configureTimezone($connection, $config);
//为数据库会话设置sql mode
$this->setModes($connection, $config);
return $connection;
这样就通过连接器与PHP底层的PDO交互连接上数据库了。
Connection
所有类型数据库的Connection类都是继承了Connection父类:
class MySqlConnection extends Connection
......
class Connection implements ConnectionInterface
public function __construct($pdo, $database = '', $tablePrefix = '', array $config = [])
$this->pdo = $pdo;
$this->database = $database;
$this->tablePrefix = $tablePrefix;
$this->config = $config;
$this->useDefaultQueryGrammar();
$this->useDefaultPostProcessor();
......
public function table($table)
return $this->query()->from($table);
......
public function query()
return new QueryBuilder(
$this, $this->getQueryGrammar(), $this->getPostProcessor()
);
......
Connection就是DatabaseManager代理的数据库连接对象了, 所以最开始执行的代码DB::table('users')->get()经过我们上面讲的历程,最终是由Connection来完成执行的,table方法返回了一个QueryBuilder对象,这个对象里定义里那些我们经常用到的where, get, first等方法, 它会根据调用的方法生成对应的SQL语句,最后通过Connection对象执行来获得最终的结果。 详细内容我们等到以后讲查询构建器的时候再看。
总结
说的东西有点多,我们来总结下文章里讲到的Database的这几个组件的角色
名称
作用
DB
DatabaseManager的静态代理
DatabaseManager
Database面向外部的接口,应用中所有与Database有关的操作都是通过与这个接口交互来完成的。
ConnectionFactory
创建数据库连接对象的类工厂
Connection
数据库连接对象,执行数据库操作最后都是通过它与PHP底层的PDO交互来完成的
Connector
作为Connection的成员专门负责通过PDO连接数据库
我们需要先理解了这几个组件的作用,在这些基础之上再去顺着看查询构建器的代码。
本文已经收录在系列文章Laravel源码学习里,欢迎访问阅读。
php源码学习laravel阅读 5.1k更新于 2020-02-07 赞4收藏4分享本作品系原创,采用《署名-非商业性使用-禁止演绎 4.0 国际》许可协议网管叨bi叨公众号:网管叨bi叨 | Golang、PHP、Laravel、Docker等学习经验分享关注专栏被 1 篇内容引用Laravel核心解读--完结篇2Kevin5k 声望1.5k 粉丝关注作者0 条评论得票最新提交评论评论支持部分 Markdown 语法:**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用。你还可以使用 @ 来通知其他用户。推荐阅读线上Go项目的Docker镜像应该怎么构建?Go开发的程序在编译成二进制文件后是可以在没有安装Go环境的系统里执行的,如果只把编译完的二进制文件直接放到镜像里就能节省很多镜像空间了。我给的回复是文章的侧重点是Kubernetes的实践所以镜像方面就没有占...Kevin赞 1阅读 4.6kOne 一个简洁的博客、微博客系统代码:[链接]文档:[链接]系统预览首页:微博列表:微博详细:文章列表:文章详细:归档:搜索,目前只能依据分类、标签搜索😀:管理后台:Eyeswap赞 45阅读 2.2k评论 1怎样用 PHP 来实现枚举?在数学和计算机科学理论中,一个集的枚举是列出某些有穷序列集的所有成员的程序,或者是一种特定类型对象的计数。这两种类型经常(但不总是)重叠。枚举是一个被命名的整型常数的集合,枚举在日常生活中很常见,...唯一丶赞 25阅读 6.1k评论 4PHP 性能终极 Debug - 生成火焰图2012 年刚开始学习 PHP,那个时候的 PHP 应用很简单,没有太多复杂的设计模式,像依赖注入,工厂模式这些还几乎没有,Reflection API 那时也才刚出来,一个 PHP 应用就是一些包了前端代码的脚本文件,正是因为 PH...路易港赞 5阅读 3.4kLaravel : Syntax error or access violation: 1055 Error?上面这样一段代码, 测试服务器很好, 上线后报错了.Syntax error or access violation: 1055 Error : MySQL : isn't in GROUP BY云云小金子赞 7阅读 12k评论 4golang实现php里的serialize()和unserialize()序列和反序列方法Golang 实现 PHP里的 serialize() 、 unserialize()安装 {代码...} 用法 {代码...} github地址:[链接]JonLee赞 2阅读 6.6k郑方方打怪升级日记 — 初识HTML5与CSS3任务名称:响应式砸蛋页面任务背景前辈:方方啊,最近项目也没什么事情,你看这个砸蛋页面不是很好看,要不你做一个响应式砸蛋页面吧?系统:郑方方接下前辈的任务 - 郑方方自动解析任务步骤任务:响应式砸蛋页面HTML5与C...郑方方赞 1阅读 3.1k评论 3Kevin5k 声望1.5k 粉丝关注作者宣传栏文章目录跟随▲44产品热门问答热门专栏热门课程最新活动翻译酷工作课程Java 开发课程PHP 开发课程Python 开发课程前端开发课程移动开发课程资源每周精选用户排行榜帮助中心建议反馈合作关于我们广告投放职位发布讲师招募联系我们合作伙伴关注产品技术日志社区运营日志市场运营日志团队日志社区访谈条款服务协议隐私政策下载 AppCopyright © 2011-2022 SegmentFault. 当前呈现版本 22.12.19浙ICP备15005796号-2浙公网安备33010602002000号ICP 经营许可 浙B2-20201554杭州堆栈科技有限公司版权所有