摘要:第三步注冊工廠啟動數據庫服務數據庫服務的啟動主要設置的連接分析器,讓能夠用服務連接數據庫。
在我們學習和使用一個開發框架時,無論使用什么框架,如何連接數據庫、對數據庫進行增刪改查都是學習的重點,在Laravel中我們可以通過兩種方式與數據庫進行交互:
DB, DB是與PHP底層的PDO直接進行交互的,通過查詢構建器提供了一個方便的接口來創建及運行數據庫查詢語句。
Eloquent Model, Eloquent是建立在DB的查詢構建器基礎之上,對數據庫進行了抽象的ORM,功能十分豐富讓我們可以避免寫復雜的SQL語句,并用優雅的方式解決了數據表之間的關聯關系。
上面說的這兩個部分都包括在了Illuminate/Database包里面,除了作為Laravel的數據庫層Illuminate/Database還是一個PHP數據庫工具集, 在任何項目里你都可以通過composer install illuminate/databse安裝并使用它。
Database服務注冊和初始化Database也是作為一種服務注冊到服務容器里提供給Laravel應用使用的,它的服務提供器是IlluminateDatabaseDatabaseServiceProvider
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 IlluminateDatabaseConnection */ 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源碼學習里,歡迎訪問閱讀。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/28542.html
摘要:過去一年時間寫了多篇文章來探討了我認為的框架最核心部分的設計思路代碼實現。為了大家閱讀方便,我把這些源碼學習的文章匯總到這里。數據庫算法和數據結構這些都是編程的內功,只有內功深厚了才能解決遇到的復雜問題。 過去一年時間寫了20多篇文章來探討了我認為的Larave框架最核心部分的設計思路、代碼實現。通過更新文章自己在軟件設計、文字表達方面都有所提高,在剛開始決定寫Laravel源碼分析地...
摘要:系統的核心是由的認證組件的看守器和提供器組成。使用的認證系統,幾乎所有東西都已經為你配置好了。其配置文件位于,其中包含了用于調整認證服務行為的注釋清晰的選項配置。 用戶認證系統(基礎介紹) 使用過Laravel的開發者都知道,Laravel自帶了一個認證系統來提供基本的用戶注冊、登錄、認證、找回密碼,如果Auth系統里提供的基礎功能不滿足需求還可以很方便的在這些基礎功能上進行擴展。這篇...
摘要:通過裝載看守器和用戶提供器裝載看守器和用戶提供器用到的方法比較多,用文字描述不太清楚,我們通過注解這個過程中用到的方法來看具體的實現細節。 用戶認證系統的實現細節 上一節我們介紹來Laravel Auth系統的基礎知識,說了他的核心組件都有哪些構成,這一節我們會專注Laravel Auth系統的實現細節,主要關注Auth也就是AuthManager是如何裝載認證用的看守器(Guard)...
摘要:為關聯關系設置約束子模型的等于父模型的上面設置的字段的值子類實現這個抽象方法通過上面代碼看到創建實例時主要是做了一些配置相關的操作,設置了子模型父模型兩個模型的關聯字段和關聯的約束。不過當查詢父模型時,可以預加載關聯數據。 Database 模型關聯 上篇文章我們主要講了Eloquent Model關于基礎的CRUD方法的實現,Eloquent Model中除了基礎的CRUD外還有一個...
摘要:外觀模式的目的在于降低系統的復雜程度。在不引入抽象外觀類的情況下,增加新的子系統可能需要修改外觀類或客戶端的源代碼,違背了開閉原則。 外觀模式 外觀模式(Facade Pattern):外部與一個子系統的通信必須通過一個統一的外觀對象進行,為子系統中的一組接口提供一個一致的界面,外觀模式定義了一個高層接口,這個接口使得這一子系統更加容易使用。外觀模式又稱為門面模式,它是一種對象結構型模...
閱讀 1741·2023-04-25 23:43
閱讀 912·2021-11-24 09:39
閱讀 717·2021-11-22 15:25
閱讀 1718·2021-11-22 12:08
閱讀 1089·2021-11-18 10:07
閱讀 2077·2021-09-23 11:22
閱讀 3343·2021-09-22 15:23
閱讀 2486·2021-09-13 10:32