為什么要引入環境的概念呢?
讓我們假設這種非常典型的情形:你們是一個開發團隊,成員有Alice, Bob, Charlie等很多人, 你們在自己的計算機上進行開發。其中,Alice使用Linux操作系統,Bob使用Mac OS,而Charlie使用Windows, 每個人使用的IDE也不盡相同。這都沒問題,PHP可以在不同平臺上跑得很好。
問題其實不在PHP,不在Yii。 問題在于每個成員在開發的同時,都在自己本地進行編程,都在本地搭建了自己的開發和測試環境。 因此,每個人用于所用的數據庫名稱、用戶名、密碼都是根據自己的喜好命名的。 這此信息是作為配置項保存在Yii應用的配置文件中的。
這在代碼的版本控制上,出現了麻煩。 每次各成員向代碼庫提交代碼時,由于他們都修改了配置文件中的用戶名、密碼等信息為本地信息。 因此,在提交代碼時,產生了沖突。雖然這個沖突不難解決,但是負責維護代碼庫的Alice覺得這很無聊,也很苦惱。 同時,Bob是個敏感的人,他認為自己本地的數據庫連接信息包含了用戶名、密碼等信息,關系到自己的安全, 不應該提交到團隊的代碼倉庫中去。于是,每次提交前,他都要把這些信息從配置文件中刪除,然后再提交。 提交完了再改回來。不然代碼連不上數據庫呀。因此,Bob也很苦惱。 而Charlie是負責把經過測試的代碼部署到產品服務器上。 由于開發端的環境和產品端的完全不同,于是每次部署前,他不得不小心地對照原來產品端的配置文件, 將開發端的配置,改成產品端的。
于是Bob提議,將配置文件排除在代碼庫之外,不對配置文件進行版本控制。 這樣Alice不用去解決無聊的代碼沖突了,Bob也不用每次都刪除配置再提交了, Charlie也不用每次拉取代碼后第一件事就是把數據庫連接信息改成服務端的了。
這確實解決了一些問題。但不得不說,你們的代碼庫是不完整的,里面缺少一個環境配置文件。 更為要命的事,某天你們的團隊經過討論,認為需要在配置文件中加入新的配置項,或者修改一個配置項, 那么你們將不得不通知所有成員,自己手動更新。因為你們未能將該配置文件納入版本管理,不能實現自動分發。
毫無疑問,這些來回來去運行環境的切換,一定煩透你了。于是貼心的Yii引入了環境的概念來解決這個問題。 其實,Yii1.1中還沒有這個特性,Yii2.0的基礎模板應用也沒這個功能。 這是Yii2.0高級模板引入的技術。 在實際運用中,各開發團隊,特別是大型團隊,已經摸索出自己的一套環境配置和部署的操作辦法了, Yii2.0環境功能,也是需要與團隊現有的環境部署規則相契合的,并非另起一爐。
## 環境的目錄結構[](http://www.digpage.com/environment.html#id2 "Permalink to this headline")
首先了解Yii各環境文件。前面我們講到,每個Yii環境就是一組配置文件, 包含了入口腳本?index.php和各類配置文件。 其實他們都放在?/path/to/digpage.com/environments?目錄下面,我們看看這個目錄都有哪些東西
~~~
.
├── dev
│?? ├── backend
│?? │?? ├── config
│?? │?? │?? ├── main-local.php
│?? │?? │?? └── params-local.php
│?? │?? └── web
│?? │?? ├── index-test.php
│?? │?? └── index.php
│?? ├── common
│?? │?? └── config
│?? │?? ├── main-local.php
│?? │?? └── params-local.php
│?? ├── console
│?? │?? └── config
│?? │?? ├── main-local.php
│?? │?? └── params-local.php
│?? ├── frontend
│?? │?? ├── config
│?? │?? │?? ├── main-local.php
│?? │?? │?? └── params-local.php
│?? │?? └── web
│?? │?? ├── index-test.php
│?? │?? └── index.php
│?? └── yii
├── prod
│?? ├── backend
│?? │?? ├── config
│?? │?? │?? ├── main-local.php
│?? │?? │?? └── params-local.php
│?? │?? └── web
│?? │?? └── index.php
│?? ├── common
│?? │?? └── config
│?? │?? ├── main-local.php
│?? │?? └── params-local.php
│?? ├── console
│?? │?? └── config
│?? │?? ├── main-local.php
│?? │?? └── params-local.php
│?? ├── frontend
│?? │?? ├── config
│?? │?? │?? ├── main-local.php
│?? │?? │?? └── params-local.php
│?? │?? └── web
│?? │?? └── index.php
│?? └── yii
└── index.php
~~~
從上面的目錄結構圖中,可以看到,環境目錄下有3個東東:
* 目錄?dev
* 目錄?prod
* 文件?index.php
其中,?dev?和?prod?結構相同,分別又包含了4個目錄和1個文件:
* frontend?目錄,用于前臺的應用,包含了存放配置文件的?config?目錄和存放web入口腳本的web?目錄
* backend?目錄,用于后臺應用,內容與?frontend?相同
* console?目錄,用于命令行應用,僅包含了?config?目錄,因為命令行應用不需要web入口腳本, 因此沒有?web?目錄。
* common?目錄,用于各web應用和命令行應用通用的環境配置,僅包含了?config?目錄, 因為不同應用不可能共用相同的入口腳本。 注意這個?common?的層級低于環境的層級,也就是說,他的通用,僅是某一環境下通用,并非所有環境下通用。
* yii?文件,是命令行應用的入口腳本文件。
環境目錄下的?index.php?并不是通常所說的web入口腳本,它其實是個定義文件。 定義了可以使用的環境,打開這個文件看一眼:
~~~
return [
'Development' => [
'path' => 'dev',
'setWritable' => [
'backend/runtime',
'backend/web/assets',
'frontend/runtime',
'frontend/web/assets',
],
'setExecutable' => [
'yii',
],
'setCookieValidationKey' => [
'backend/config/main-local.php',
'frontend/config/main-local.php',
],
],
'Production' => [
'path' => 'prod',
'setWritable' => [
'backend/runtime',
'backend/web/assets',
'frontend/runtime',
'frontend/web/assets',
],
'setExecutable' => [
'yii',
],
'setCookieValidationKey' => [
'backend/config/main-local.php',
'frontend/config/main-local.php',
],
],
];
~~~
不用深入去研究,也可以大致猜到它定義了?Development?Production?兩個環境, 聰明如你,肯定用腳都能猜得出來。其中:
* path?指明了當前環境所對應的配置文件存放目錄。如 Productions 環境對應的目錄為?prod?。
* setWritable?指明了需要?init?腳本設定為可寫模式的目錄,這些目錄Yii應用在運行時會寫入。
* setExecutable?指明了要將哪個文件設為可執行。這個文件就是命令行應用的入口文件了。
* setCookieValidationsKey?指明了向哪個文件寫入?cookieValidationKey?配置項。
對于分散于各處的?web?和?config?目錄而言,它們也是有共性的。
* 凡是?web?目錄,存放的都是web應用的入口腳本,一個?index.php?和一個測試版本的index-test.php
* 凡是?config?目錄,存放的,都是本地配置信息?main-local.php?和?params-local.php
## 環境配置的生效規則[](http://www.digpage.com/environment.html#id3 "Permalink to this headline")
說了這么多,現在串起來看。運行?init?腳本就會將某一環境的系列文件復制到當前的文件中, 這些文件就是?index.php?yii?入口文件和?*-local.php?配置文件。 復制到哪呢?復制到了/path/to/digpage.com/?目錄下面, 并覆蓋?frontend?backend?console?common?中對應的?config?目錄和入口腳本 (?index.php?或?yii?,?common?中沒有入口腳本) 。
在初始情況下,即未運行過?init?腳本之前,各應用目錄(frontend, beckend, console)和通用目錄(common), 都是已經有一些配置文件了。就是config目錄下的?bootstrap.php?main.phpparams.php?。
總的講,?*.php?與環境、配置相關的文件都有哪些呢?有表示主配置的?main.php?main-local.php?, 有表示全局參數的?params.php?params-local.php?,表示引導階段的?bootstrap.php?, 有表示入口腳本的?yii?和?index.php?。其中,?bootstrap.php?在?[_別名(Alias)_](http://www.digpage.com/aliases.html#aliases)?中有介紹, 且在優先順序與其他配置文件的原則是一樣的,下面就不再重復講了。
運行了?init?腳本后,環境中的文件也被復制出來。 這些配置文件成套地分布在各應用目錄和通用目錄的 config 目錄下。 而?index.php?入口腳本則分布在各應用目錄的 web 目錄下,?yii?入口腳本則只放在應用根目錄下。
入口腳本我們在?[_入口文件index.php_](http://www.digpage.com/app_struct.html#entry-script)?已經講了,這里就不講。剩下的,來看看配置文件們。 其中,所有的?*-local.php?都來自于你選用的環境,表示本地配置的意思。他們不會被寫入到代碼倉庫中。 當然,這些環境,也就是整個?/path/to/digpage.com/environments?目錄都會被寫入代碼倉庫。
而所有不帶?*-local.php?的main和params配置文件,都不是環境的內容。但在最終的運行環境中,他們是起作用的。
上面講到的配置文件有很多,有前臺、后臺、命令行和common的,有帶local的、不帶local的, 有params、main等,看起來好復雜的樣子。那么一個環境發生作用時,這些文件是怎么個順序呢? 這要看看?[_入口文件index.php_](http://www.digpage.com/app_struct.html#entry-script)?部分的內容,但總的原則是:
* 前臺、后臺和命令行的配置文件間,互不干擾,各管各的。沒有先后順序一說。 因為Yii在任意時間,要么是在跑前臺,要么是在跑后臺。還記得么?他們是不同的應用,他們是獨立的。 但是,這里有個common,通用于前臺、后臺等。common的內容被前臺或后臺的覆蓋。
* local和不帶local的。明顯的,local的是本地配置文件,不帶local的是團隊間通用的配置。 因此,local的覆蓋不帶local的。
* params, main。這2類文件表示的配置內容并不重疊,他們邏輯上不存在誰覆蓋誰的問題。 如果看看源代碼,可以發現,params只是main配置的一部分。 而main的內容,是作為參數傳遞給應用的構造函數。 因此,這兩者不存在誰覆蓋誰的問題。
## 環境的使用[](http://www.digpage.com/environment.html#id4 "Permalink to this headline")
環境在具體使用上,把握這么幾個原則:
* 與前后臺無關,且與環境無關的配置項,寫到?digpage.com\common\config\main.php?中去。 不要寫到環境中去,也不要寫到前臺或后臺的配置文件中去。 比如,當使用?FileCache?作為緩存時,這是與環境無關、與前后臺無關的, 或者說所有環境下,前后臺的配置都相同。 有關配置項要寫到?digpage.com\common\config\main.php?中去。
* 無關環境的配置,不要寫到環境中去,寫到應用的配置中去。 如,應用的ID,無論是開發環境還是產品環境,ID是不會變的。但前后臺ID是不同的。 因此,ID的配置項寫到digpage.com\frontend\config\main.php?中去。 而不要寫到digpage.com\environment\frontend\config\main-local.php?中去。
* 與環境有關,但與前后臺無關的配置項,要寫到環境的 common 配置中去。 比如,有的應用前后臺使用的數據庫是一致的,因此,其?db?配置項應該是一樣的。 但在開發時,所使用的數據庫服務與產品時的數據庫服務肯定是不一樣的。 這種情況下,要所配置項寫到digpage.com\environments\dev\common\main-local.php
* 環境配置文件只提供框架。凡是環境配置文件,對于敏感信息,如,數據庫地址、用戶名、密碼、API Key等信息, 一律留空,供團隊成員在調用?init?后自行填寫。
* 本地配置絕不提交代碼庫。因此,所有的應用目錄(frontend, backend等,并非environment目錄)下的, 所有?*-local.php?都不提交代碼庫。這點Yii已經通過?.gitignore?為我們做好了。 關于?.gitignore?的有關信息,可以看看?[Git文檔](http://git-scm.com/doc)?的有關內容。
這樣做能達到什么效果呢?
* 整個代碼倉庫中,不會有任何的敏感信息。這個代碼倉庫即使被外部獲取,其危害程度也僅限于代碼。 數據庫、Web Service 等密碼還不至于泄露。
* 增加、刪除、更新配置項的所有修改,都可以通過版本控制系統向整個團隊分發。
* 所有團隊成員只需要記住權限范圍內的敏感信息,就可以完成工作。 本地開發的隊友,只要有本地的數據庫連接信息就可以開發。 負責測試的隊友,只需要知道測試數據庫服務器連接信息就可進行測試。 負責部署的隊友,只需要知道產品數據庫的連接信息就可以完成部署。 所有團隊成員只需要記住各自權限范圍內有限的幾個敏感信息就可以了。
不足之處就是每次 pull 代碼時,如果配置文件有更新,需要團隊成員調用?init?將新的配置文件覆蓋本地配置。 然后需要手動填入敏感信息。但這種情況在初期配置不太穩定的情況下,根據團隊迭代頻率,一般一天一次。 而等開發進入正常階段后,配置文件相對穩定,極少有需要修改的。
## 注意 cookieValidationKey[](http://www.digpage.com/environment.html#cookievalidationkey "Permalink to this headline")
另外還有一個需要讀者朋友們留意的地方,就是每次調用?init?腳本切換、更新環境配置時,cookieValidationKey?都會被重新生成的隨機串所覆蓋。這往往會導致更新前后?cookieValidationKey不一致。 從安全角度來講,定時不定時地更新?cookieValidationKey?也是無可厚非的。 但同時我們也要看到由此產生的一個副作用,那就是原先保存在用戶機器上的cookie在下次訪問時,會全部失效。 一個簡單表現就是已經設置了自動登錄的用戶,在更新?cookieValidationKey?后,全部需要重新登錄。
這在大部分情況下,我們認為這是可以接受的。相信大家在使用互聯網的過程中,也有遇到過重新登錄的情況。 因此,通常我們也可以很放心的使用?init?腳本,而不必特別地去關注自動生成的cookieValidationKey?。
這種情況下,?cookieValidationKey?是一個純粹的、與本地環境密切相關的配置項,我們不用太操心它。
但是,有的情況下,?cookieValidationKey?則不宜由?init?腳本來自動生成,而需要運維人員人工進行干預。
需要人工干預的情況,一般出現在對cookie要求比較嚴格的場景。 如,當你的應用采用分布式架構提供服務,同時運行在多個節點的時候。 有的負載均衡策略會將同一用戶的先后2次請求隨機分配給不同的節點進行處理。 而如果這兩個節點的?cookieValidationKey?不一致,那么就會出現用戶就會收到很奇怪的錯誤信息。
因此,在分布式情況下,最簡便的處理方式還是通過人工干預,確保各節點的?cookieValidationKey始終一致。
這種情況下,?cookieValidationKey?已經不是一個純粹的本地環境配置項了,最多算是一個環境級別的配置項, 而與本地、本機沒有多大關系了。這種情況下, 應當將?cookieValidationKey?與其他諸如數據庫密碼等敏感信息等同視之,由具有權限的、負責運維的人員掌握。 并在每次調用?init?腳本后,將所掌握的?cookieValidationKey?填寫到相應的配置項中去。
這里舍棄掉了init腳本自動生成的?cookieValidationKey?。 更新前后,?cookieValidationKey?未發生改變,因此,對于應用的用戶而言沒什么影響。
幸運的是,絕大多數情況下,我們還無需對于?cookieValidationKey?操太多的心,可以完全由init腳本自動生成。
其實,對于Yii開發團隊而言,剛開始的時候,是將 cookieValidationKey 放在非環境配置文件 main.php 中的。 后來,覺得這個配置項與雖然不一定是本地相關的,但起碼與環境有關, 因此后來還是放在環境配置文件main-local.php中。
只是,由于安全上的考慮,對于未設置?cookieValidationKey?的情況會拋出異常。 同時,又為了方便開發者,又采用了讓?init?腳本生成隨機串的方式來自動配置。
如果覺得《深入理解Yii2.0》對您有所幫助,也請[幫助《深入理解Yii2.0》](http://www.digpage.com/donate.html#donate)。 謝謝!
- 更新記錄
- 導讀
- Yii是什么
- Yii2.0的亮點
- 背景知識
- 如何閱讀本書
- Yii基礎
- 屬性(Property)
- 事件(Event)
- 行為(Behavior)
- Yii約定
- Yii應用的目錄結構和入口腳本
- 別名(Alias)
- Yii的類自動加載機制
- 環境和配置文件
- 配置項(Configuration)
- Yii模式
- MVC
- 依賴注入和依賴注入容器
- 服務定位器(Service Locator)
- 請求與響應(TBD)
- 路由(Route)
- Url管理
- 請求(Reqeust)
- Web應用Request
- Yii與數據庫(TBD)
- 數據類型
- 事務(Transaction)
- AcitveReocrd事件和關聯操作
- 樂觀鎖與悲觀鎖
- 《深入理解Yii2.0》視頻教程
- 第一講:基礎配置
- 第二講:用戶登錄
- 第三講:文章及評論的模型
- 附錄
- 附錄1:Yii2.0 對比 Yii1.1 的重大改進
- 附錄2:Yii的安裝
- 熱心讀者