laravel5.5源码笔记(七、数据库初始化)
laravel中的数据库也是以服务提供者进行初始化的名为databaseserviceprovider,在config文件的providers数组中有写。路径为vendor\laravel\framework\src\illuminate\database\databaseserviceprovider.php
跟以往的serviceprovider一样在register方法中注册,在boot方法中引导加载。
来看一下register方法。为了保险起见它先通过model将之前的加载数据给清除掉了。随后开始注册各种数据库连接所用到的对象,通过singleton方法注册一个单例的延迟加载对象到容器中。将db门面类绑定到了databasemanager类中。
public function register() { model::clearbootedmodels(); $this->registerconnectionservices(); $this->registereloquentfactory(); $this->registerqueueableentityresolver(); } protected function registerconnectionservices() { // the connection factory is used to create the actual connection instances on // the database. we will inject the factory into the manager so that it may // make the connections while they are actually needed and not of before. $this->app->singleton('db.factory', function ($app) { return new connectionfactory($app); }); // the database manager is used to resolve various connections, since multiple // connections might be managed. it also implements the connection resolver // interface which may be used by other components requiring connections. $this->app->singleton('db', function ($app) { return new databasemanager($app, $app['db.factory']); }); $this->app->bind('db.connection', function ($app) { return $app['db']->connection(); }); } /** * register the eloquent factory instance in the container. * * @return void */ 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') ); }); } /** * register the queueable entity resolver implementation. * * @return void */ protected function registerqueueableentityresolver() { $this->app->singleton(entityresolver::class, function () { return new queueentityresolver; }); }
数据库连接对象connectionfactory。这个连接工厂类之中的各种方法都是在创建配置,以及通过配置数组,返回对应的数据库连接实例。这个类中的方法大多是对数据库的连接做一些配置,然后根据这些配置来返回相应的数据库连接实例。
/** * create a new connection instance. * * @param string $driver * @param \pdo|\closure $connection * @param string $database * @param string $prefix * @param array $config * @return \illuminate\database\connection * * @throws \invalidargumentexception */ protected function createconnection($driver, $connection, $database, $prefix = '', array $config = []) { if ($resolver = connection::getresolver($driver)) { return $resolver($connection, $database, $prefix, $config); } switch ($driver) { case 'mysql': return new mysqlconnection($connection, $database, $prefix, $config); case 'pgsql': return new postgresconnection($connection, $database, $prefix, $config); case 'sqlite': return new sqliteconnection($connection, $database, $prefix, $config); case 'sqlsrv': return new sqlserverconnection($connection, $database, $prefix, $config); } throw new invalidargumentexception("unsupported driver [$driver]"); }
数据库管理对象databasemanager。这个数据库管理类之中的各种方法也是通过各种数据库配置来调用connectionfactory工厂来返回数据库连接实例,它会通过配置read,write来返回相应的读写pdo实例。以及包含了数据库实例的创建与断开销毁等。set、get各种配置。如setpdofortype方法来设置数据库连接的读写分离(设置只读、只写)。那么这个类的上游方法在哪里呢。它是从哪里被调用的呢?我们开头提了一句,db门面类所绑定的类,就是这个类,但是如果我们去这个类中寻找常用的talbe()、query()等方法,肯定是一无所获的,不过我们会发现__call()方法,这个魔术方法会在调用不存在的方法时执行,看一下它的内容。它只有一句代码,从$this->connection()这个对象中,执行相应的方法并返回结果。是的,laravel的源码封装度太高了,这里我们暂时只需要知道$this->connection()代表了数据库连接实例就好。
public function __call($method, $parameters) { return $this->connection()->$method(...$parameters); }
刚刚说到数据库连接实例,现在我们就来探寻这个实例是如何被创建出来的。如下makeconnection方法所示,通过刚刚说到的connectionfactory来返回数据库连接实例。
/** * prepare the read / write mode for database connection instance. * * @param \illuminate\database\connection $connection * @param string $type * @return \illuminate\database\connection */ protected function setpdofortype(connection $connection, $type = null) { if ($type == 'read') { $connection->setpdo($connection->getreadpdo()); } elseif ($type == 'write') { $connection->setreadpdo($connection->getpdo()); } return $connection; } /** * make the database connection instance. * * @param string $name * @return \illuminate\database\connection */ protected function makeconnection($name) { $config = $this->configuration($name); // first we will check by the connection name to see if an extension has been // registered specifically for that connection. if it has we will call the // closure and pass it the config allowing it to resolve the connection. if (isset($this->extensions[$name])) { return call_user_func($this->extensions[$name], $config, $name); } // next we will check to see if an extension has been registered for a driver // and will call the closure if so, which allows us to have a more generic // resolver for the drivers themselves which applies to all connections. if (isset($this->extensions[$driver = $config['driver']])) { return call_user_func($this->extensions[$driver], $config, $name); } return $this->factory->make($config, $name); }
好的,看到databasemanager如何创建出数据库连接实例,又要把视线跳到之前说的connectionfactory类中了。$this->factory->make($config, $name);最后返回了make方法,我们就从这个方法入手,请看下列代码。
/** * establish a pdo connection based on the configuration. * * @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); } /** * create a single database connection instance. * * @param array $config * @return \illuminate\database\connection */ protected function createsingleconnection(array $config) { $pdo = $this->createpdoresolver($config); return $this->createconnection( $config['driver'], $pdo, $config['database'], $config['prefix'], $config ); } /** * create a new connection instance. * * @param string $driver * @param \pdo|\closure $connection * @param string $database * @param string $prefix * @param array $config * @return \illuminate\database\connection * * @throws \invalidargumentexception */ protected function createconnection($driver, $connection, $database, $prefix = '', array $config = []) { if ($resolver = connection::getresolver($driver)) { return $resolver($connection, $database, $prefix, $config); } switch ($driver) { case 'mysql': return new mysqlconnection($connection, $database, $prefix, $config); case 'pgsql': return new postgresconnection($connection, $database, $prefix, $config); case 'sqlite': return new sqliteconnection($connection, $database, $prefix, $config); case 'sqlsrv': return new sqlserverconnection($connection, $database, $prefix, $config); } throw new invalidargumentexception("unsupported driver [$driver]"); }
可以看到,经过一系列的方法跳转,我们终于通过数据库配置,得到了mysql等数据库的连接对象。
数据库工厂类connectionfactory返回的实例连接,我们拿mysql举例。返回的便是mysqlconnection这个类\vendor\laravel\framework\src\illuminate\database\mysqlconnection.php
进入这个类文件,可以看见都是获取grammar相关的方法,这些方法暂时先不去看它。秘密在于它的父类connection类。(\vendor\laravel\framework\src\illuminate\database\connection.php)先看它的构造方法,从这个方法我们可以知道,所有模型对象也好,db对象也好,底层都是通过pdo去连接执行的,另外呢,tableprefix数据表前缀,以及数据库连接配置也是在这里进行加载的。usedefaultquerygrammar这俩个方法就不去深究了。我们来看点有趣的。
/** * create a new database connection instance. * * @param \pdo|\closure $pdo * @param string $database * @param string $tableprefix * @param array $config * @return void */ public function __construct($pdo, $database = '', $tableprefix = '', array $config = []) { $this->pdo = $pdo; // first we will setup the default properties. we keep track of the db // name we are connected to since it is needed when some reflective // type commands are run such as checking whether a table exists. $this->database = $database; $this->tableprefix = $tableprefix; $this->config = $config; // we need to initialize a query grammar and the query post processors // which are both very important parts of the database abstractions // so we initialize these to their default values while starting. $this->usedefaultquerygrammar(); $this->usedefaultpostprocessor(); }
有趣的是什么呢?就是这个table()方法了。我们都知道,在laravel中既可以使用模型的方式去进行数据库操作,也可以使用db::table()的方式,而我们平时使用较多的table()方法的真身,就在这个类里了。这个table方法只做了一件事,就是从$this->query()这个对象中from一个表,然后返回的对象就可以通过链式调用的方式去做其他操作了。接下来看这个$this->query(),这个方法也只做了一件事,new一个querybuilder对象。
/** * begin a fluent query against a database table. * * @param string $table * @return \illuminate\database\query\builder */ public function table($table) { return $this->query()->from($table); } /** * get a new query builder instance. * * @return \illuminate\database\query\builder */ public function query() { return new querybuilder( $this, $this->getquerygrammar(), $this->getpostprocessor() ); }
而querybuilder这个对象就是我们平时使用的eloquent orm 的入口,我们平时使用的那些方便的数据库操作方法都是从这里进入。
通过一系列跳转,我们会发现,这个querybuilder的真身在\vendor\laravel\framework\src\illuminate\database\query\builder.php这个类中,名为builder类。来到这个类文件中,稍微浏览一下,感觉发现了宝藏,里面那些where()、join()、get()、find()方法,不正是我们常用的各种方便的orm方法么?
来来回回绕了这么大一个圈终于找到,我们常用的函数是从何而来,但是现在还有一个疑问了。现在我们一路跟踪到的线索,都是从db::table()这种方式跟踪而来,那么model的方式是怎么调用的呢?
让我们随便新建一个模型类,然后找到它的父类model,vendor\laravel\framework\src\illuminate\database\eloquent\model.php
浏览一番过后,我们发现了比较眼熟的fill()、all()、save()等方法,然后我们会发现这些方法中,大部分都有$this->newmodelquery();这么一句,我们根据这一线索一路跟踪,一路尾行,最终我们会发现new querybuilder这句代码又出现了。
/** * get a new query builder for the model's table. * * @return \illuminate\database\eloquent\builder */ public function newquery() { return $this->registerglobalscopes($this->newquerywithoutscopes()); } /** * register the global scopes for this builder instance. * * @param \illuminate\database\eloquent\builder $builder * @return \illuminate\database\eloquent\builder */ public function registerglobalscopes($builder) { foreach ($this->getglobalscopes() as $identifier => $scope) { $builder->withglobalscope($identifier, $scope); } return $builder; } /** * get a new query builder instance for the connection. * * @return \illuminate\database\query\builder */ protected function newbasequerybuilder() { $connection = $this->getconnection(); return new querybuilder( $connection, $connection->getquerygrammar(), $connection->getpostprocessor() ); }
是的,再次跳转后,我们便又回到了\vendor\laravel\framework\src\illuminate\database\query\builder.php这个类中。
至于那些model里没写的方法为什么可以直接调用?你去model类里找一找看有没有__call这个魔术方法,看它里面写了些什么
/** * handle dynamic method calls into the model. * * @param string $method * @param array $parameters * @return mixed */ public function __call($method, $parameters) { if (in_array($method, ['increment', 'decrement'])) { return $this->$method(...$parameters); } return $this->newquery()->$method(...$parameters); }
到这里,数据库服务是怎么启动的,db门面、model类为什么能直接执行orm方法,相信我们已经有清晰的认识了。至于orm是怎么转化成sql语句执行的,且听下回分解~
上一篇: iOS/OSX漏洞分析和再现:CVE-2019-7286
下一篇: 社会生活中爆笑尬事不少呢!
推荐阅读
-
laravel5.5源码笔记(三、门面类facade)
-
laravel5.5源码笔记(八、Eloquent ORM)
-
laravel5.5源码笔记(一、入口应用的初始化)
-
Laravel5.5源码详解 -- 一次查询的详细执行:从Auth-Login-web中间件到数据库查询结果的全过程
-
laravel5.5源码笔记(六、中间件)
-
laravel5.5源码笔记(四、路由)
-
laravel5.5源码笔记(二、服务提供者provider)
-
SpringCloud源码学习笔记之Eureka客户端——初始化
-
laravel5.5源码笔记(七、数据库初始化)
-
Ocelot简易教程(七)之配置文件数据库存储插件源码解析