在咱们学习和使用一个开发框架时,不管使用什么框架,如何链接数据库、对数据库进行增删改查都是学习的重点,在Laravel中咱们能够经过两种方式与数据库进行交互:php
DB
, DB
是与PHP底层的PDO
直接进行交互的,经过查询构建器提供了一个方便的接口来建立及运行数据库查询语句。Eloquent Model
, Eloquent
是创建在DB
的查询构建器基础之上,对数据库进行了抽象的ORM
,功能十分丰富让咱们能够避免写复杂的SQL语句,并用优雅的方式解决了数据表之间的关联关系。上面说的这两个部分都包括在了Illuminate/Database
包里面,除了做为Laravel的数据库层Illuminate/Database
仍是一个PHP数据库工具集, 在任何项目里你均可以经过composer install illuminate/databse
安装并使用它。mysql
Database也是做为一种服务注册到服务容器里提供给Laravel应用使用的,它的服务提供器是Illuminate\Database\DatabaseServiceProvider
laravel
public function register() { Model::clearBootedModels(); $this->registerConnectionServices(); $this->registerEloquentFactory(); $this->registerQueueableEntityResolver(); }
第一步:Model::clearBootedModels()
。在 Eloquent 服务启动以前为了保险起见须要清理掉已经booted的Model和全局查询做用域git
/** * Clear the list of booted models so they will be re-booted. * * @return void */ public static function clearBootedModels() { static::$booted = []; static::$globalScopes = []; }
第二步:注册ConnectionServicesgithub
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建立了数据库链接实例,最后执行数据库的增删改查是由数据库链接实例来完成的。sql
第三步:注册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,用于监听数据库的事件。app
上面说了DatabaseManager是整个数据库服务的接口,咱们经过DB
门面进行操做的时候实际上调用的就是DatabaseManager,它会经过数据库链接对象工厂(ConnectionFacotry)得到数据库链接对象(Connection),而后数据库链接对象会进行具体的CRUD操做。咱们先看一下DatabaseManager的构造函数:composer
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); }
上面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。
在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父类:
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源码学习里,欢迎访问阅读。