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語句執行的,且聽下回分解~