說到配置項,讀者朋友們第一反應是不是Yii的配置文件?這是一段配置文件的代碼:
~~~
return [
'id' => 'app-frontend',
'basePath' => dirname(__DIR__),
'bootstrap' => ['log'],
'controllerNamespace' => 'frontend\controllers',
'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,
],
],
],
],
'params' => [...],
];
~~~
Yii中許多地方都要用到配置項,Yii應用自身和其他幾乎一切類對象的創建、初始化、配置都要用到配置項。 配置項是針對對象而言的,也就是說,配置項一定是用于配置某一個對象,用于初始化或配置對象的屬性。 關于屬性的有關內容,請查看?[_屬性(Property)_](http://www.digpage.com/property.html#property)?。
## 配置項的格式[](http://www.digpage.com/configuration.html#id2 "Permalink to this headline")
一個配置文件包含了3個部分:
* 基本信息配置。主要指如?id?basePath?等這些應用的基本信息,主要是一些簡單的字符串。
* components配置。配置文件的主體,也是我們接下來要講的配置項。
* params配置。主要是提供一些全局參數。
我們一般講的配置項是指component配置項及里面的子項。 簡單來講,一個配置項采用下面的格式:
~~~
[
'class' => 'path\to\ClassName',
'propertyName' => 'propertyValue',
'on eventName' => $eventHandler,
'as behaviorName' => $behaviorConfig,
]
~~~
作為配置項:
* 配置項以數組進行組織。
* class?數組元素表示將要創建的對象的完整類名。
* propertyName?數組元素表示指定為?propertyName?屬性的初始值為?$propertyValue?。
* on?eventName?數組元素表示將?$eventHandler?綁定到對象的?eventName?事件中。
* as?behaviorName?數組元素表示用?$behaviorConfig?創建一個行為,并注入到對象中。 這里的$behaviroConfig?也是一個配置項;
* 配置項可以嵌套。
其中,?class?元素僅在特定的情況下可以沒有。就是使用配置數組的時候,其類型已經是確定的。 這往往是用于重新配置一個已經存在的對象, 或者是在創建對象時,使用了?new?或Yii::createObject()?指定了類型。 除此以外的大多數情況?class?都是配置數組的必備元素:
~~~
// 使用 new 時指定了類型,配置數組中就不應再有 class 元素
$connection = new \yii\db\Connection([
'dsn' => $dsn,
'username' => $username,
'password' => $password,
]);
// 使用 Yii::createObject()時,如果第一個參數指定了類型,也不應在配置數
// 組中設定 class
$db = Yii::createObject('yii\db\Connection', [
'dsn' => 'mysql:host=127.0.0.1;dbname=demo',
'username' => 'root',
'password' => '',
'charset' => 'utf8',
]);
// 對現有的對象重新配置時,也不應在配置數組中設定 class
Yii::configure($db, [
'dsn' => 'mysql:host=127.0.0.1;dbname=demo',
'username' => 'root',
'password' => '',
'charset' => 'utf8',
]);
~~~
上面的例子中,在沒看到配置數組的內容前,已經可以確定對象的類型了。 這種其他情況下,配置數組中如果再有一個 class 元素來設定類型的話,就不合理了。 這種情況下,配置數組不能有 class 元素。 但除此以外的其他情況,均要求配置數組提供 class 元素,以表示要創建的對象的類型。
## 配置項產生作用的原理[](http://www.digpage.com/configuration.html#id3 "Permalink to this headline")
從?[_環境和配置文件_](http://www.digpage.com/environment.html#environment)?部分的內容,我們了解到了一個Yii應用,特別是高級模版應用,是具有許多個配置文件的, 這些配置文件在入口腳本?index.php?中被引入, 然后按照一定的規則合并成一個配置數組?$config?并用于創建Application對象。 具體可以看看?[_入口文件index.php_](http://www.digpage.com/app_struct.html#entry-script)?部分的內容。在入口腳本中,調用了:
~~~
$application = new yii\web\Application($config);
~~~
在?yii\web\Application?中,會調用父類的構造函數?yii\base\Application::__construct($config)?, 來創建Web Application。在這個構造函數中:
~~~
public function __construct($config = [])
{
Yii::$app = $this;
$this->setInstance($this);
$this->state = self::STATE_BEGIN;
// 預處理配置項
$this->preInit($config);
$this->registerErrorHandler($config);
// 使用 yii\base\Component::__construct() 完成構建
Component::__construct($config);
}
~~~
可以看到,其實分成兩步,一是對?$config?進行預處理, 二是使用yii\base\Component::__construct($config)?進行構建。
### 配置項預處理[](http://www.digpage.com/configuration.html#id4 "Permalink to this headline")
預處理配置項的?yii\base\Application::preInit()?方法其實在?[_別名(Alias)_](http://www.digpage.com/aliases.html#aliases)?部分講過, 當時主要是從預定義別名的角度來講的。現在我們再來完整地看看這個方法和有關的屬性:
~~~
// basePath屬性,由Application的父類yii\base\Module定義,并提供getter和setter
private $_basePath;
// runtimePath屬性和vendorPath屬性,Application都為其定義了getter和setter。
private $_runtimePath;
private $_vendorPath;
// 還有一個timeZone屬性,Application為其提供了getter和setter,但不提供存
// 儲變量。
// 而是分別調用 PHP 的 date_default_timezone_get() 和
// date_default_timezone_set()
public function preInit(&$config)
{
// 配置數組中必須指定應用id,這里僅判斷,不賦值。
if (!isset($config['id'])) {
throw new InvalidConfigException(
'The "id" configuration for the Application is required.');
}
// 設置basePath屬性,這個屬性在Application的父類 yii\base\Module 中定義。
// 在完成設置后,刪除配置數組中的 basePath 配置項
if (isset($config['basePath'])) {
$this->setBasePath($config['basePath']);
unset($config['basePath']);
} else {
throw new InvalidConfigException(
'The "basePath" configuration for the Application is required.');
}
// 設置vendorPath屬性,并在設置后,刪除$config中的相應配置項
if (isset($config['vendorPath'])) {
$this->setVendorPath($config['vendorPath']);
unset($config['vendorPath']);
} else {
// set "@vendor"
$this->getVendorPath();
}
// 設置runtimePath屬性,并在設置后,刪除$config中的相應配置項
if (isset($config['runtimePath'])) {
$this->setRuntimePath($config['runtimePath']);
unset($config['runtimePath']);
} else {
// set "@runtime"
$this->getRuntimePath();
}
// 設置timeZone屬性,并在設置后,刪除$config中的相應配置項
if (isset($config['timeZone'])) {
$this->setTimeZone($config['timeZone']);
unset($config['timeZone']);
} elseif (!ini_get('date.timezone')) {
$this->setTimeZone('UTC');
}
// 將coreComponents() 所定義的核心組件配置,與開發者通過配置文件定義
// 的組件配置進行合并。
// 合并中,開發者配置優先,核心組件配置起補充作用。
foreach ($this->coreComponents() as $id => $component) {
// 配置文件中沒有的,使用核心組件的配置
if (!isset($config['components'][$id])) {
$config['components'][$id] = $component;
// 配置文件中有的,但并未指組件的class的,使用核心組件的class
} elseif (is_array($config['components'][$id]) &&
!isset($config['components'][$id]['class'])) {
$config['components'][$id]['class'] = $component['class'];
}
}
}
~~~
從上面的代碼可以看出,這個?preInit()?對配置數組?$config?作了以下處理:
* id?屬性是必不可少的。
* 從?$config?中拿掉了?basePath?runtimePath?vendorPath?和?timeZone?4個屬性的配置項。 當然,也設置了相應的屬性。
* 對?$config['components']?配置項進行兩方面的補充。 一是配置文件中沒有的,而核心組件有的,把核心組件的配置信息補充進去。 二是配置文件中雖然也有,但沒有指定組件的class的,使用核心組件配置信息指定的class。
基于此,我們不難得出如下結論:
* 有的配置項如?id?是不可少的,有的配置項如?basePath?等不用我們設置也是有默認值的。
* 對于核心組件,我們不配置也可以使用。
* 核心組件的ID是提前安排好的,沒有充足的理由一般不要改變他,否則以后接手的人會罵你的。
* 核心組件可以不指明?class?,默認會使用預先安排的類型。
對于核心組件,不同的應用有不同的安排,這個我們可以看看,大致了解下,具體在于各應用的coreComponents()?中定義:
~~~
// yii\base\Application 的核心組件
public function coreComponents()
{
return [
'log' => ['class' => 'yii\log\Dispatcher'], // 日志組件
'view' => ['class' => 'yii\web\View'], // 視圖組件
'formatter' => ['class' => 'yii\i18n\Formatter'], // 格式組件
'i18n' => ['class' => 'yii\i18n\I18N'], // 國際化組件
'mailer' => ['class' => 'yii\swiftmailer\Mailer'], // 郵件組件
'urlManager' => ['class' => 'yii\web\UrlManager'], // url管理組件
'assetManager' => ['class' => 'yii\web\AssetManager'], // 前端資源管理組件
'security' => ['class' => 'yii\base\Security'], // 安全組件
];
}
// yii\web\Application 的核心組件,在基類的基礎上加入Web應用必需的組件
public function coreComponents()
{
return array_merge(parent::coreComponents(), [
'request' => ['class' => 'yii\web\Request'], // HTTP請求組件
'response' => ['class' => 'yii\web\Response'], // HTTP響應組件
'session' => ['class' => 'yii\web\Session'], // session組件
'user' => ['class' => 'yii\web\User'], // 用戶管理組件
'errorHandler' => ['class' => 'yii\web\ErrorHandler'], // 錯誤處理組件
]);
}
// yii\console\Application 的核心組件,
public function coreComponents()
{
return array_merge(parent::coreComponents(), [
'request' => ['class' => 'yii\console\Request'], // 命令行請求組件
'response' => ['class' => 'yii\console\Response'], // 命令行響應組件
'errorHandler' => ['class' => 'yii\console\ErrorHandler'], // 錯誤處理組件
]);
}
~~~
這些我們大致有個印象就夠了,不用刻意去記住,用著用著你就自然記住了。
### 使用配置數組構造應用[](http://www.digpage.com/configuration.html#id5 "Permalink to this headline")
在使用?preInit()?完成配置數組的預處理之后, Application構造函數又直接調用yii\base\Component::__construct()?來構造Application對象。
結果這個?yii\base\Component::__construct()?也是個推委扯皮的家伙,他根本就沒自己定義。 而是直接繼承了父類的?yii\base\Object::__construct()?。因此,Application構造函數的最后一步, 實際上調用的是?yii\base\Object::__construct($config)?。 這個函數的原理,我們在?[_Object的配置方法_](http://www.digpage.com/property.html#object-config)?部分已經作出解釋,這里就不再重復。
只是這里有兩類特殊的配置項需要注意,就是以?on?*?打頭的事件和以?as?*?打頭的行為。 對于事件行為,可以閱讀?[_事件(Event)_](http://www.digpage.com/event.html#event)?和?[_行為(Behavior)_](http://www.digpage.com/behavior.html#behavior)?部分的內容。
Yii對于這兩類配置項的處理,是在?yii\base\Component::__set()?中完成的,從Component開始, 才支持事件和行為。具體處理的代碼如下:
~~~
public function __set($name, $value)
{
$setter = 'set' . $name;
if (method_exists($this, $setter)) {
$this->$setter($value);
return;
// 'on ' 打頭的配置項在這里處理
} elseif (strncmp($name, 'on ', 3) === 0) {
// 對于 'on event' 配置項,將配置值作為事件 handler 綁定到 evnet 上去
$this->on(trim(substr($name, 3)), $value);
return;
// 'as ' 打頭的配置項在這里處理
} elseif (strncmp($name, 'as ', 3) === 0) {
// 對于 'as behavior' 配置項,將配置值作為創建Behavior的配置,創
// 建后綁定為 behavior
$name = trim(substr($name, 3));
$this->attachBehavior($name, $value instanceof Behavior ? $value
: Yii::createObject($value));
return;
} else {
$this->ensureBehaviors();
foreach ($this->_behaviors as $behavior) {
if ($behavior->canSetProperty($name)) {
$behavior->$name = $value;
return;
}
}
}
if (method_exists($this, 'get' . $name)) {
throw new InvalidCallException('Setting read-only property: ' .
get_class($this) . '::' . $name);
} else {
throw new UnknownPropertyException('Setting unknown property: '
. get_class($this) . '::' . $name);
}
}
~~~
從上面的代碼中可以看到,對于?on?event?形式配置項,Yii視配置值為一個事件handler,綁定到event?上。 而對于?as?behavior?形式的配置項,視配置值為一個Behavior,注入到當前實例中,并冠以?behavior?的名稱。
如果覺得《深入理解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的安裝
- 熱心讀者