摘要:第三步注冊工廠啟動(dòng)數(shù)據(jù)庫服務(wù)數(shù)據(jù)庫服務(wù)的啟動(dòng)主要設(shè)置的連接分析器,讓能夠用服務(wù)連接數(shù)據(jù)庫。
在我們學(xué)習(xí)和使用一個(gè)開發(fā)框架時(shí),無論使用什么框架,如何連接數(shù)據(jù)庫、對(duì)數(shù)據(jù)庫進(jìn)行增刪改查都是學(xué)習(xí)的重點(diǎn),在Laravel中我們可以通過兩種方式與數(shù)據(jù)庫進(jìn)行交互:
DB, DB是與PHP底層的PDO直接進(jìn)行交互的,通過查詢構(gòu)建器提供了一個(gè)方便的接口來創(chuàng)建及運(yùn)行數(shù)據(jù)庫查詢語句。
Eloquent Model, Eloquent是建立在DB的查詢構(gòu)建器基礎(chǔ)之上,對(duì)數(shù)據(jù)庫進(jìn)行了抽象的ORM,功能十分豐富讓我們可以避免寫復(fù)雜的SQL語句,并用優(yōu)雅的方式解決了數(shù)據(jù)表之間的關(guān)聯(lián)關(guān)系。
上面說的這兩個(gè)部分都包括在了Illuminate/Database包里面,除了作為Laravel的數(shù)據(jù)庫層Illuminate/Database還是一個(gè)PHP數(shù)據(jù)庫工具集, 在任何項(xiàng)目里你都可以通過composer install illuminate/databse安裝并使用它。
Database服務(wù)注冊和初始化Database也是作為一種服務(wù)注冊到服務(wù)容器里提供給Laravel應(yīng)用使用的,它的服務(wù)提供器是IlluminateDatabaseDatabaseServiceProvider
public function register() { Model::clearBootedModels(); $this->registerConnectionServices(); $this->registerEloquentFactory(); $this->registerQueueableEntityResolver(); }
第一步:Model::clearBootedModels()。在 Eloquent 服務(wù)啟動(dòng)之前為了保險(xiǎn)起見需要清理掉已經(jīng)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用來創(chuàng)建數(shù)據(jù)庫連接實(shí)例,它將被注入到DatabaseManager中,在講服務(wù)容器綁定時(shí)就說過了依賴注入的其中一個(gè)作用是延遲初始化對(duì)象,所以只要在用到數(shù)據(jù)庫連接實(shí)例時(shí)它們才會(huì)被創(chuàng)建。
db DatabaseManger 作為Database面向外部的接口,DB這個(gè)Facade就是DatabaseManager的靜態(tài)代理。應(yīng)用中所有與Database有關(guān)的操作都是通過與這個(gè)接口交互來完成的。
db.connection 數(shù)據(jù)庫連接實(shí)例,是與底層PDO接口進(jìn)行交互的底層類,可用于數(shù)據(jù)庫的查詢、更新、創(chuàng)建等操作。
所以DatabaseManager作為接口與外部交互,在應(yīng)用需要時(shí)通過ConnectionFactory創(chuàng)建了數(shù)據(jù)庫連接實(shí)例,最后執(zhí)行數(shù)據(jù)庫的增刪改查是由數(shù)據(jù)庫連接實(shí)例來完成的。
第三步:注冊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") ); }); }
啟動(dòng)數(shù)據(jù)庫服務(wù)
public function boot() { Model::setConnectionResolver($this->app["db"]); Model::setEventDispatcher($this->app["events"]); }
數(shù)據(jù)庫服務(wù)的啟動(dòng)主要設(shè)置 Eloquent Model 的連接分析器(connection resolver),讓model能夠用db服務(wù)連接數(shù)據(jù)庫。還有就是設(shè)置數(shù)據(jù)庫事件的分發(fā)器 dispatcher,用于監(jiān)聽數(shù)據(jù)庫的事件。
DatabaseManager上面說了DatabaseManager是整個(gè)數(shù)據(jù)庫服務(wù)的接口,我們通過DB門面進(jìn)行操作的時(shí)候?qū)嶋H上調(diào)用的就是DatabaseManager,它會(huì)通過數(shù)據(jù)庫連接對(duì)象工廠(ConnectionFacotry)獲得數(shù)據(jù)庫連接對(duì)象(Connection),然后數(shù)據(jù)庫連接對(duì)象會(huì)進(jìn)行具體的CRUD操作。我們先看一下DatabaseManager的構(gòu)造函數(shù):
public function __construct($app, ConnectionFactory $factory) { $this->app = $app; $this->factory = $factory; }
ConnectionFactory是在上面介紹的綁定db服務(wù)的時(shí)候傳遞給DatabaseManager的。比如我們現(xiàn)在程序里執(zhí)行了DB::table("users")->get(), 在DatabaseManager里并沒有table方法然后就會(huì)觸發(fā)魔術(shù)方法__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方法會(huì)返回?cái)?shù)據(jù)庫連接對(duì)象,這個(gè)過程首先是解析連接名稱parseConnectionName
protected function parseConnectionName($name) { $name = $name ?: $this->getDefaultConnection(); // 檢查connection name 是否以::read, ::write結(jié)尾 比如"ucenter::read" return Str::endsWith($name, ["::read", "::write"]) ? explode("::", $name, 2) : [$name, null]; } public function getDefaultConnection() { // laravel默認(rèn)是mysql,這里假定是常用的mysql連接 return $this->app["config"]["database.default"]; }
如果沒有指定連接名稱,Laravel會(huì)使用database配置里指定的默認(rèn)連接名稱, 接下來makeConnection方法會(huì)根據(jù)連接名稱來創(chuàng)建連接實(shí)例:
protected function makeConnection($name) { //假定$name是"mysql", 從config/database.php中獲取"connections.mysql"的配置 $config = $this->configuration($name); //首先去檢查在應(yīng)用啟動(dòng)時(shí)是否通過連接名注冊了extension(閉包), 如果有則通過extension獲得連接實(shí)例 //比如在AppServiceProvider里通過DatabaseManager::extend("mysql", function () {...}) if (isset($this->extensions[$name])) { return call_user_func($this->extensions[$name], $config, $name); } //檢查是否為連接配置指定的driver注冊了extension, 如果有則通過extension獲得連接實(shí)例 if (isset($this->extensions[$driver])) { return call_user_func($this->extensions[$driver], $config, $name); } // 通過ConnectionFactory數(shù)據(jù)庫連接對(duì)象工廠獲取Mysql的連接類 return $this->factory->make($config, $name); }ConnectionFactory
上面makeConnection方法使用了數(shù)據(jù)庫連接對(duì)象工程來獲取數(shù)據(jù)庫連接對(duì)象,我們來看一下工廠的make方法:
/** * 根據(jù)配置創(chuàng)建一個(gè)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向配置參數(shù)中添加默認(rèn)的 prefix 屬性與 name 屬性。
接下來根據(jù)配置文件中是否設(shè)置了讀寫分離。如果設(shè)置了讀寫分離,那么就會(huì)調(diào)用 createReadWriteConnection 函數(shù),生成具有讀、寫兩個(gè)功能的 connection;否則的話,就會(huì)調(diào)用 createSingleConnection 函數(shù),生成普通的連接對(duì)象。
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]"); }
創(chuàng)建數(shù)據(jù)庫連接的方法createConnection里參數(shù)$pdo是一個(gè)閉包:
function () use ($config) { return $this->createConnector($config)->connect($config); };
這就引出了Database服務(wù)中另一部份連接器Connector, Connection對(duì)象是依賴連接器連接上數(shù)據(jù)庫的,所以在探究Connection之前我們先來看看連接器Connector。
Connector在illuminate/database中連接器Connector是專門負(fù)責(zé)與PDO交互連接數(shù)據(jù)庫的,我們接著上面講到的閉包參數(shù)$pdo往下看
createConnector方法會(huì)創(chuàng)建連接器:
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連接數(shù)據(jù)庫時(shí)用的DSN連接字符串 $dsn = $this->getDsn($config); //獲取要傳給PDO的選項(xiàng)參數(shù) $options = $this->getOptions($config); //創(chuàng)建一個(gè)PDO連接對(duì)象 $connection = $this->createConnection($dsn, $config, $options); if (! empty($config["database"])) { $connection->exec("use `{$config["database"]}`;"); } //為連接設(shè)置字符集和collation $this->configureEncoding($connection, $config); //設(shè)置time zone $this->configureTimezone($connection, $config); //為數(shù)據(jù)庫會(huì)話設(shè)置sql mode $this->setModes($connection, $config); return $connection; } }
這樣就通過連接器與PHP底層的PDO交互連接上數(shù)據(jù)庫了。
Connection所有類型數(shù)據(jù)庫的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代理的數(shù)據(jù)庫連接對(duì)象了, 所以最開始執(zhí)行的代碼DB::table("users")->get()經(jīng)過我們上面講的歷程,最終是由Connection來完成執(zhí)行的,table方法返回了一個(gè)QueryBuilder對(duì)象,這個(gè)對(duì)象里定義里那些我們經(jīng)常用到的where, get, first等方法, 它會(huì)根據(jù)調(diào)用的方法生成對(duì)應(yīng)的SQL語句,最后通過Connection對(duì)象執(zhí)行來獲得最終的結(jié)果。 詳細(xì)內(nèi)容我們等到以后講查詢構(gòu)建器的時(shí)候再看。
總結(jié)說的東西有點(diǎn)多,我們來總結(jié)下文章里講到的Database的這幾個(gè)組件的角色
名稱 | 作用 |
---|---|
DB | DatabaseManager的靜態(tài)代理 |
DatabaseManager | Database面向外部的接口,應(yīng)用中所有與Database有關(guān)的操作都是通過與這個(gè)接口交互來完成的。 |
ConnectionFactory | 創(chuàng)建數(shù)據(jù)庫連接對(duì)象的類工廠 |
Connection | 數(shù)據(jù)庫連接對(duì)象,執(zhí)行數(shù)據(jù)庫操作最后都是通過它與PHP底層的PDO交互來完成的 |
Connector | 作為Connection的成員專門負(fù)責(zé)通過PDO連接數(shù)據(jù)庫 |
我們需要先理解了這幾個(gè)組件的作用,在這些基礎(chǔ)之上再去順著看查詢構(gòu)建器的代碼。
本文已經(jīng)收錄在系列文章Laravel源碼學(xué)習(xí)里,歡迎訪問閱讀。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/28542.html
摘要:過去一年時(shí)間寫了多篇文章來探討了我認(rèn)為的框架最核心部分的設(shè)計(jì)思路代碼實(shí)現(xiàn)。為了大家閱讀方便,我把這些源碼學(xué)習(xí)的文章匯總到這里。數(shù)據(jù)庫算法和數(shù)據(jù)結(jié)構(gòu)這些都是編程的內(nèi)功,只有內(nèi)功深厚了才能解決遇到的復(fù)雜問題。 過去一年時(shí)間寫了20多篇文章來探討了我認(rèn)為的Larave框架最核心部分的設(shè)計(jì)思路、代碼實(shí)現(xiàn)。通過更新文章自己在軟件設(shè)計(jì)、文字表達(dá)方面都有所提高,在剛開始決定寫Laravel源碼分析地...
摘要:系統(tǒng)的核心是由的認(rèn)證組件的看守器和提供器組成。使用的認(rèn)證系統(tǒng),幾乎所有東西都已經(jīng)為你配置好了。其配置文件位于,其中包含了用于調(diào)整認(rèn)證服務(wù)行為的注釋清晰的選項(xiàng)配置。 用戶認(rèn)證系統(tǒng)(基礎(chǔ)介紹) 使用過Laravel的開發(fā)者都知道,Laravel自帶了一個(gè)認(rèn)證系統(tǒng)來提供基本的用戶注冊、登錄、認(rèn)證、找回密碼,如果Auth系統(tǒng)里提供的基礎(chǔ)功能不滿足需求還可以很方便的在這些基礎(chǔ)功能上進(jìn)行擴(kuò)展。這篇...
摘要:通過裝載看守器和用戶提供器裝載看守器和用戶提供器用到的方法比較多,用文字描述不太清楚,我們通過注解這個(gè)過程中用到的方法來看具體的實(shí)現(xiàn)細(xì)節(jié)。 用戶認(rèn)證系統(tǒng)的實(shí)現(xiàn)細(xì)節(jié) 上一節(jié)我們介紹來Laravel Auth系統(tǒng)的基礎(chǔ)知識(shí),說了他的核心組件都有哪些構(gòu)成,這一節(jié)我們會(huì)專注Laravel Auth系統(tǒng)的實(shí)現(xiàn)細(xì)節(jié),主要關(guān)注Auth也就是AuthManager是如何裝載認(rèn)證用的看守器(Guard)...
摘要:為關(guān)聯(lián)關(guān)系設(shè)置約束子模型的等于父模型的上面設(shè)置的字段的值子類實(shí)現(xiàn)這個(gè)抽象方法通過上面代碼看到創(chuàng)建實(shí)例時(shí)主要是做了一些配置相關(guān)的操作,設(shè)置了子模型父模型兩個(gè)模型的關(guān)聯(lián)字段和關(guān)聯(lián)的約束。不過當(dāng)查詢父模型時(shí),可以預(yù)加載關(guān)聯(lián)數(shù)據(jù)。 Database 模型關(guān)聯(lián) 上篇文章我們主要講了Eloquent Model關(guān)于基礎(chǔ)的CRUD方法的實(shí)現(xiàn),Eloquent Model中除了基礎(chǔ)的CRUD外還有一個(gè)...
摘要:外觀模式的目的在于降低系統(tǒng)的復(fù)雜程度。在不引入抽象外觀類的情況下,增加新的子系統(tǒng)可能需要修改外觀類或客戶端的源代碼,違背了開閉原則。 外觀模式 外觀模式(Facade Pattern):外部與一個(gè)子系統(tǒng)的通信必須通過一個(gè)統(tǒng)一的外觀對(duì)象進(jìn)行,為子系統(tǒng)中的一組接口提供一個(gè)一致的界面,外觀模式定義了一個(gè)高層接口,這個(gè)接口使得這一子系統(tǒng)更加容易使用。外觀模式又稱為門面模式,它是一種對(duì)象結(jié)構(gòu)型模...
閱讀 1749·2023-04-25 23:43
閱讀 930·2021-11-24 09:39
閱讀 728·2021-11-22 15:25
閱讀 1727·2021-11-22 12:08
閱讀 1097·2021-11-18 10:07
閱讀 2082·2021-09-23 11:22
閱讀 3352·2021-09-22 15:23
閱讀 2507·2021-09-13 10:32