<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                跟DI容器類似,引入Service Locator目的也在于解耦。有許多成熟的設計模式也可用于解耦,但在Web應用上, Service Locator絕對占有一席之地。 對于Web開發而言,Service Locator天然地適合使用, 主要就是因為Service Locator模式非常貼合Web這種基于服務和組件的應用的運作特點。 這一模式的優點有: * Service Locator充當了一個運行時的鏈接器的角色,可以在運行時動態地修改一個類所要選用的服務, 而不必對類作任何的修改。 * 一個類可以在運行時,有針對性地增減、替換所要用到的服務,從而得到一定程度的優化。 * 實現服務提供方、服務使用方完全的解耦,便于獨立測試和代碼跨框架復用。 ## Service Locator的基本功能[](http://www.digpage.com/service_locator.html#id2 "Permalink to this headline") 在Yii中Service Locator由?yii\di\ServiceLocator?來實現。 從代碼組織上,Yii將Service Locator放到與DI同一層次來對待,都組織在?yii\di?命名空間下。 下面是Service Locator的源代碼: ~~~ class ServiceLocator extends Component { // 用于緩存服務、組件等的實例 private $_components = []; // 用于保存服務和組件的定義,通常為配置數組,可以用來創建具體的實例 private $_definitions = []; // 重載了 getter 方法,使得訪問服務和組件就跟訪問類的屬性一樣。 // 同時,也保留了原來Component的 getter所具有的功能。 // 請留意,ServiceLocator 并未重載 __set(), // 仍然使用 yii\base\Component::__set() public function __get($name) { ... ... } // 對比Component,增加了對是否具有某個服務和組件的判斷。 public function __isset($name) { ... ... } // 當 $checkInstance === false 時,用于判斷是否已經定義了某個服務或組件 // 當 $checkInstance === true 時,用于判斷是否已經有了某人服務或組件的實例 public function has($id, $checkInstance = false) { return $checkInstance ? isset($this->_components[$id]) : isset($this->_definitions[$id]); } // 根據 $id 獲取對應的服務或組件的實例 public function get($id, $throwException = true) { ... ... } // 用于注冊一個組件或服務,其中 $id 用于標識服務或組件。 // $definition 可以是一個類名,一個配置數組,一個PHP callable,或者一個對象 public function set($id, $definition) { ... ... } // 刪除一個服務或組件 public function clear($id) { unset($this->_definitions[$id], $this->_components[$id]); } // 用于返回Service Locator的 $_components 數組或 $_definitions 數組, // 同時也是 components 屬性的getter函數 public function getComponents($returnDefinitions = true) { ... ... } // 批量方式注冊服務或組件,同時也是 components 屬性的setter函數 public function setComponents($components) { ... ... } } ~~~ 從代碼可以看出,Service Locator繼承自?yii\base\Component?,這是Yii中的一個基礎類, 提供了屬性、事件、行為等基本功能,關于Component的有關知識,可以看看?[_屬性(Property)_](http://www.digpage.com/property.html#property)?、?[_事件(Event)_](http://www.digpage.com/event.html#event)?和?[_行為(Behavior)_](http://www.digpage.com/behavior.html#behavior)?。 Service Locator 通過?__get()?__isset()?has()?等方法, 擴展了?yii\base\Component?的最基本功能,提供了對于服務和組件的屬性化支持。 從功能來看,Service Locator提供了注冊服務和組件的?set()?setComponents()?等方法, 用于刪除的clear()?。用于讀取的?get()?和?getComponents()?等方法。 細心的讀者可能一看到?setComponents()?和?getComponents()?就猜到了, Service Locator還具有一個可讀寫的 components 屬性。 ### Service Locator的數據結構[](http://www.digpage.com/service_locator.html#id3 "Permalink to this headline") 從上面的代碼中,可以看到Service Locator維護了兩個數組,?$_components?和?$_definitions?。這兩個數組均是以服務或組件的ID為鍵的數組。 其中,?$_components?用于緩存存Service Locator中的組件或服務的實例。 Service Locator 為其提供了getter和setter。使其成為一個可讀寫的屬性。?$_definitions?用于保存這些組件或服務的定義。這個定義可以是: * 配置數組。在向Service Locator索要服務或組件時,這個數組會被用于創建服務或組件的實例。 與DI容器的要求類似,當定義是配置數組時,要求配置數組必須要有?class?元素,表示要創建的是什么類。不然你讓Yii調用哪個構造函數? * PHP callable。每當向Service Locator索要實例時,這個PHP callable都會被調用,其返回值,就是所要的對象。 對于這個PHP callable有一定的形式要求,一是它要返回一個服務或組件的實例。 二是它不接受任何的參數。 至于具體原因,后面會講到。 * 對象。這個更直接,每當你索要某個特定實例時,直接把這個對象給你就是了。 * 類名。即,使得?is_callable($definition,?true)?為真的定義。 從?yii\di\ServiceLocator::set()?的代碼: ~~~ public function set($id, $definition) { // 當定義為 null 時,表示要從Service Locator中刪除一個服務或組件 if ($definition === null) { unset($this->_components[$id], $this->_definitions[$id]); return; } // 確保服務或組件ID的唯一性 unset($this->_components[$id]); // 定義如果是個對象或PHP callable,或類名,直接作為定義保存 // 留意這里 is_callable的第二個參數為true,所以,類名也可以。 if (is_object($definition) || is_callable($definition, true)) { // 定義的過程,只是寫入了 $_definitions 數組 $this->_definitions[$id] = $definition; // 定義如果是個數組,要確保數組中具有 class 元素 } elseif (is_array($definition)) { if (isset($definition['class'])) { // 定義的過程,只是寫入了 $_definitions 數組 $this->_definitions[$id] = $definition; } else { throw new InvalidConfigException( "The configuration for the \"$id\" component must contain a \"class\" element."); } // 這也不是,那也不是,那么就拋出異常吧 } else { throw new InvalidConfigException( "Unexpected configuration type for the \"$id\" component: " . gettype($definition)); } } ~~~ 服務或組件的ID在Service Locator中是唯一的,用于區別彼此。在任何情況下,Service Locator中同一ID只有一個實例、一個定義。也就是說,Service Locator中,所有的服務和組件,只保存一個單例。 這也是正常的邏輯,既然稱為服務定位器,你只要給定一個ID,它必然返回一個確定的實例。這一點跟DI容器是一樣的。 Service Locator 中ID僅起標識作用,可以是任意字符串,但通常用服務或組件名稱來表示。 如,以db?來表示數據庫連接,以?cache?來表示緩存組件等。 至于批量注冊的?yii\di\ServiceLocator::setCompoents()?只不過是簡單地遍歷數組,循環調用?set()而已。 就算我不把代碼貼出來,像你這么聰明的,一下子就可以自己寫出來了。 向Service Locator注冊服務或組件,其實就是向?$_definitions?數組寫入信息而已。 ### 訪問Service Locator中的服務[](http://www.digpage.com/service_locator.html#id4 "Permalink to this headline") Service Locator重載了?__get()?使得可以像訪問類的屬性一樣訪問已經實例化好的服務和組件。 下面是重載的?__get()?方法: ~~~ public function __get($name) { // has() 方法就是判斷 $_definitions 數組中是否已經保存了服務或組件的定義 // 請留意,這個時候服務或組件僅是完成定義,不一定已經實例化 if ($this->has($name)) { // get() 方法用于返回服務或組件的實例 return $this->get($name); // 未定義的服務或組件,那么視為正常的屬性、行為, // 調用 yii\base\Component::__get() } else { return parent::__get($name); } } ~~~ 在注冊好了服務或組件定義之后,就可以像訪問屬性一樣訪問這些服務(組件)。 前提是已經完成注冊,不要求已經實例化。 訪問這些服務或屬性,被轉換成了調用?yii\di\ServiceLocator::get()?來獲取實例。 下面是使用這種形式訪問服務或組件的例子: ~~~ // 創建一個Service Locator $serviceLocator = new yii\di\ServiceLocator; // 注冊一個 cache 服務 $serviceLocator->set('cache', [ 'class' => 'yii\cache\MemCache', 'servers' => [ ... ... ], ]); // 使用訪問屬性的方法訪問這個 cache 服務 $serviceLocator->cache->flushValues(); // 上面的方法等效于下面這個 $serviceLocator->get('cache')->flushValues(); ~~~ 在Service Locator中,并未重載?__set()?。所以,Service Locator中的服務和組件看起來就好像只讀屬性一樣。 要向Service Locator中“寫”入服務和組件,沒有 setter 可以使用,需要調用yii\di\ServiceLocator::set()?對服務和組件進行注冊。 ## 通過Service Locator獲取實例[](http://www.digpage.com/service_locator.html#id5 "Permalink to this headline") 與注冊服務和組件的簡單之極相反,Service Locator在創建獲取服務或組件實例的過程要稍微復雜一點。 這一點和DI容器也是很像的。 Service Locator通過?yii\di\ServiceLocator::get()?來創建、獲取服務或組件的實例: ~~~ public function get($id, $throwException = true) { // 如果已經有實例化好的組件或服務,直接使用緩存中的就OK了 if (isset($this->_components[$id])) { return $this->_components[$id]; } // 如果還沒有實例化好,那么再看看是不是已經定義好 if (isset($this->_definitions[$id])) { $definition = $this->_definitions[$id]; // 如果定義是個對象,且不是Closure對象,那么直接將這個對象返回 if (is_object($definition) && !$definition instanceof Closure) { // 實例化后,保存進 $_components 數組中,以后就可以直接引用了 return $this->_components[$id] = $definition; // 是個數組或者PHP callable,調用 Yii::createObject()來創建一個實例 } else { // 實例化后,保存進 $_components 數組中,以后就可以直接引用了 return $this->_components[$id] = Yii::createObject($definition); } } elseif ($throwException) { throw new InvalidConfigException("Unknown component ID: $id"); // 即沒實例化,也沒定義,萬能的Yii也沒辦法通過一個任意的ID, // 就給你找到想要的組件或服務呀,給你個 null 吧。 // 表示Service Locator中沒有這個ID的服務或組件。 } else { return null; } } ~~~ Service Locator創建獲取服務或組件實例的過程是: * 看看緩存數組?$_components?中有沒有已經創建好的實例。有的話,皆大歡喜,直接用緩存中的就可以了。 * 緩存中沒有的話,那就要從定義開始創建了。 * 如果服務或組件的定義是個對象,那么直接把這個對象作為服務或組件的實例返回就可以了。 但有一點要注意,當使用一個PHP callable定義一個服務或組件時,這個定義是一個Closure類的對象。 這種定義雖然也對象,但是可不能把這種對象直接當成服務或組件的實例返回。 * 如果定義是一個數組或者一個PHP callable,那么把這個定義作為參數,調用Yii::createObject()?來創建實例。 這個?Yii::createObject()?在講配置時我們介紹過,當時只是點一點,這里會講得更深一點。但別急,先放一放, 知道他能為Service Locator創建對象就OK了。我們等下還會講這個方法的。 ## 在Yii應用中使用Service Locator和DI容器[](http://www.digpage.com/service_locator.html#yiiservice-locatordi "Permalink to this headline") 我們在講DI容器時,提到了Yii中是把Service Locator和DI容器結合起來用的,Service Locator是建立在DI容器之上的。 那么一個Yii應用,是如何使用Service Locator和DI容器的呢? ### DI容器的引入[](http://www.digpage.com/service_locator.html#di "Permalink to this headline") 我們知道,每個Yii應用都有一個入口腳本?index.php?。在其中,有一行不怎么顯眼: ~~~ require(__DIR__ . '/../../vendor/yiisoft/yii2/Yii.php'); ~~~ 這一行看著普通,也就是引入一個 Yii.php 的文件。但是,讓我們來看看這個 Yii.php ~~~ <?php require(__DIR__ . '/BaseYii.php'); class Yii extends \yii\BaseYii { } spl_autoload_register(['Yii', 'autoload'], true, true); Yii::$classMap = include(__DIR__ . '/classes.php'); // 重點看這里。創建一個DI 容器,并由 Yii::$container 引用 Yii::$container = new yii\di\Container; ~~~ Yii?是一個工具類,繼承自?yii\BaseYii?。 但這里對父類的代碼沒有任何重載,意味之父類和子類在功能上其實是相同的。 但是,Yii提供了讓你修改默認功能的機會。 就是自己寫一個?Yii?類,來擴展、重載Yii默認的、由?yii\BaseYii?提供的特性和功能。 盡管實際使用中,我們還從來沒有需要改寫過這個類,主要是因為沒有必要在這里寫代碼,可以通過別的方式實現。 但Yii確實提供了這么一個可能。這個在實踐中不常用,有這么個印象就足夠了。 這里重點看最后一句代碼,創建了一個DI容器,并由?Yii::$container?引用。 也就是說,?Yii?類維護了一個DI容器,這是DI容器開始介入整個應用的標志。 同時,這也意味著,在Yii應用中,我們可以隨時使用?Yii::$container?來訪問DI容器。 一般情況下,如無必須的理由,不要自己創建DI容器,使用?Yii::$container?完全足夠。 ### Application的本質[](http://www.digpage.com/service_locator.html#application "Permalink to this headline") 再看看入口腳本?index.php?的最后兩行: ~~~ $application = new yii\web\Application($config); $application->run(); ~~~ 創建了一個?yii\web\Application?實例,并調用其?run()?方法。 那么,這個?yii\web\Application?是何方神圣? 首先,?yii\web\Application?繼承自?yii\base\Application?,這從?yii\web\Application?的代碼可以看出來 ~~~ class Application extends \yii\base\Application { ... ... } ~~~ 而?yii\base\Application?又繼承自?yii\base\Module?,說明所有的Application都是Module ~~~ abstract class Application extends Module { ... ... } ~~~ 那么?yii\base\Module?又繼承自哪個類呢?不知道你猜到沒,他繼承自?yii\di\ServiceLocator ~~~ class Module extends ServiceLocator { ... ... } ~~~ 所有的Module都是服務定位器Service Locator,因此,所有的Application也都是Service Locator。 同時,在Application的構造函數中,?yii\base\Application::__construct() ~~~ public function __construct($config = []) { Yii::$app = $this; ... ... } ~~~ 第一行代碼就把Application當前的實例,賦值給?Yii::$app?了。 這意味著Yii應用創建之后,可以隨時通過?Yii::$app?來訪問應用自身,也就是訪問Service Locator。 至此,DI容器有了,Service Locator也出現了。那么Yii是如何擺布這兩者的呢?這兩者又是如何千里姻緣一線牽的呢? ### 實例創建方法[](http://www.digpage.com/service_locator.html#id6 "Permalink to this headline") Service Locator和DI容器的親密關系就隱藏在?yii\di\ServiceLocator::get()?獲取實例時, 調用的Yii::createObject()?中。 前面我們說到這個?Yii?繼承自?yii\BaseYii?,因此這個函數實際上是BaseYii::createObject()?, 其代碼如下: ~~~ // static::$container就是上面說的引用了DI容器的靜態變量 public static function createObject($type, array $params = []) { // 字符串,代表一個類名、接口名、別名。 if (is_string($type)) { return static::$container->get($type, $params); // 是個數組,代表配置數組,必須含有 class 元素。 } elseif (is_array($type) && isset($type['class'])) { $class = $type['class']; unset($type['class']); // 調用DI容器的get() 來獲取、創建實例 return static::$container->get($class, $params, $type); // 是個PHP callable則調用其返回一個具體實例。 } elseif (is_callable($type, true)) { // 是個PHP callable,那就調用它,并將其返回值作為服務或組件的實例返回 return call_user_func($type, $params); // 是個數組但沒有 class 元素,拋出異常 } elseif (is_array($type)) { throw new InvalidConfigException( 'Object configuration must be an array containing a "class" element.'); // 其他情況,拋出異常 } else { throw new InvalidConfigException( "Unsupported configuration type: " . gettype($type)); } } ~~~ 這個?createObject()?提供了一個向DI容器獲取實例的接口, 對于不同的定義,除了PHP callable外,createObject()?都是調用了DI容器的?yii\di\Container::get()?, 來獲取實例的。Yii::createObject()?就是Service Locator和DI容器親密關系的證明, 也是Service Locator構建于DI容器之上的證明。而Yii中所有的Module, 包括Application都是Service Locator,因此,它們也都構建在DI容器之上。 同時,在Yii框架代碼中,只要創建實例,就是調用?Yii::createObject()?這個方法來實現。 可以說,Yii中所有的實例(除了Application,DI容器自身等入口腳本中實例化的),都是通過DI容器來獲取的。 同時,我們不難發現,?Yii?的基類?yii\BaseYii?,所有的成員變量和方法都是靜態的, 其中的DI容器是個靜態成員變量?$container?。 因此,DI容器就形成了最常見形式的單例模式,在內存中僅有一份,所有的Service Locator (Module和Application)都共用這個DI容器。 就就節省了大量的內存空間和反復構造實例的時間。 更為重要的是,DI容器的單例化,使得Yii不同的模塊共用組件成為可能。 可以想像,由于共用了DI容器,容器里面的內容也是共享的。因此,你可以在A模塊中改變某個組件的狀態,而B模塊中可以了解到這一狀態變化。 但是,如果不采用單例模式,而是每個模塊(Module或Application)維護一個自己的DI容器, 要實現這一點難度會大得多。 所以,這種共享DI容器的設計,是必然的,合理的。 另外,前面我們講到,當Service Locator中服務或組件的定義是一個PHP callable時,對其形式有一定要求。 一是返回一個實例,二是不接收任何參數。 這在?Yii::createObject()?中也可以看出來。 由于?Yii::createObject()?為?yii\di\ServiceLocator::get()?所調用,且沒有提供第二參數, 因此,當使用 Service Locator獲取實例時,?Yii::createObject()?的?$params?參數為空。 因此,使用call_user_func($type,?$params)?調用這個PHP callable時, 這個PHP callable是接收不到任何參數的。 ## Yii創建實例的全過程[](http://www.digpage.com/service_locator.html#yii "Permalink to this headline") 可能有的讀者朋友會有疑問:不對呀,前面講過DI容器的使用是要先注冊依賴,后獲取實例的。 但Service Locator在注冊服務、組件時,又沒有向DI容器注冊依賴。那在獲取實例的時候, DI容器怎么解析依賴并創建實例呢? 請留意,在向DI容器索要一個沒有注冊過依賴的類型時, DI容器視為這個類型不依賴于任何類型可以直接創建, 或者這個類型的依賴信息容器本身可以通過Reflection API自動解析出來,不用提前注冊。 可能還有的讀者會想:還是不對呀,在我開發Yii的過程中,又沒有寫過注冊服務的代碼: ~~~ Yii::$app->set('db', [ 'class' => 'yii\db\Connection', 'dsn' => 'mysql:host=db.digpage.com;dbname=digpage.com', 'username' => 'www.digpage.com', 'password' => 'www.digapge.com', 'charset' => 'utf8', ]); Yii::$app->set('cache', [ 'class' => 'yii\caching\MemCache', 'servers' => [ [ 'host' => 'cache1.digpage.com', 'port' => 11211, 'weight' => 60, ], [ 'host' => 'cache2.digpage.com', 'port' => 11211, 'weight' => 40, ], ], ]); ~~~ 為何可以在沒有注冊的情況下獲取服務的實例并使用服務呢? 其實,你也不是什么都沒寫,至少肯定是在某個配置文件中寫了有關的內容的: ~~~ return [ 'components' => [ 'db' => [ 'class' => 'yii\db\Connection', 'dsn' => 'mysql:host=localhost;dbname=yii2advanced', 'username' => 'root', 'password' => '', 'charset' => 'utf8', ], 'cache' => [ 'class' => 'yii\caching\MemCache', 'servers' => [ [ 'host' => 'cache1.digpage.com', 'port' => 11211, 'weight' => 60, ], [ 'host' => 'cache2.digpage.com', 'port' => 11211, 'weight' => 40, ], ], ], ... ... ], ]; ~~~ 只不過,在?[_配置項(Configuration)_](http://www.digpage.com/configuration.html#configuration)?和?[_Object的配置方法_](http://www.digpage.com/property.html#object-config)?部分, 我們了解了配置文件是如何產生作用的,配置到應用當中的。 這個數組會被?Yii::configure($config)?所調用,然后會變成調用Application的 setComponents(), 而Application其實就是一個Service Locator。setComponents()方法又會遍歷傳入的配置數組, 然后使用使用 Service Locator 的set() 方法注冊服務。 到了這里,就可以了解到:每次在配置文件的?components?項寫入配置信息, 最終都是在向Application這個 Service Locator注冊服務。 讓我們回顧一下,DI容器、Service Locator是如何配合使用的: * Yii?類提供了一個靜態的?$container?成員變量用于引用DI容器。 在入口腳本中,會創建一個DI容器,并賦值給這個?$container?。 * Service Locator通過?Yii::createObject()?來獲取實例, 而這個?Yii::createObject()?是調用了DI容器的?yii\di\Container::get()?來向?Yii::$container?索要實例的。 因此,Service Locator最終是通過DI容器來創建、獲取實例的。 * 所有的Module,包括Application都繼承自?yii\di\ServiceLocator?,都是Service Locator。 因此,DI容器和Service Locator就構成了整個Yii的基礎。 如果覺得《深入理解Yii2.0》對您有所幫助,也請[幫助《深入理解Yii2.0》](http://www.digpage.com/donate.html#donate)。 謝謝!
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看