在Yii中創建新對象或者初始化已經存在的對象廣泛的使用配置,配置通常包含被創建對象的類名和一組將要賦值給對象的屬性的初始值,這裏的屬性是Yii2的屬性。還可以在對象的事件上綁定事件處理器,或者將行爲附加到對象上。從而在定義了對象的初始值的同時,充分規定對象的運行時的動態特性。
以下代碼中的配置被用來創建並初始化一個數據庫連接:
$config = [
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=127.0.0.1;dbname=demo',
'username' => 'root',
'password' => '',
'charset' => 'utf8',
];
$db = Yii::createObject($config);
Yii::createObject()
是Yii2中最常用的用來創建對象的方法,其內容是從DI Container中去取的對象,在後面的章節中我們會講到。這個方法方法接受一個配置數組並根據數組中指定的類名創建對象,對象實例化後,剩餘的參數被用來初始化對象的屬性,事件和行爲。
小編提醒:在Yii2.1中,配置數組中用來表示類名的鍵值由
class
變成了__class
,但是配置的原理是不變的。
對於已存在的對象,可以使用 Yii::configure() 方法根據配置去初始化其屬性, 就像這樣:
Yii::configure($object, $config);
請注意,如果配置一個已存在的對象,那麼配置數組中不應該包含指定類名的 class 元素。
配置是Yii2的一個特色
在編程中,有個非常重要的概念叫“委託”,就是一個對象A可以依靠另一個對象B去完成特定的功能,典型的應用就是策略模式了。要實現“委託”,要有這麼個流程:在對象A實例化時注入另一個對象B;A持有對象B;對象A委託對象B去完成特定的功能。“注入”“持有”“委託”都是設計模式中的高頻詞彙,通過這些操作可以擴展類的功能。
我們看看在別的面嚮對象語言如Java或者PHP其他框架中經常使用的方式:
class Person
{
private $strategy = null;
public function __construct(TravelStrategy $travel)
{
$this->strategy = $travel;
}
/**
* 設置旅行的方式.
*/
public function setTravelStrategy(TravelStrategy $travel)
{
$this->strategy = $travel;
}
/**
* 旅行.
*/
public function travel()
{
//這裏實現了“委託”,委託給$this->strategy來實現旅行的具體方式
return $this->strategy->travelAlgorithm();
}
}
在實例化或者初始化時,大概就是這麼用的:
class Test
{
public function run($argument)
{
// 乘坐火車旅行
$person = new Person(new TrainStrategy());
$person->travel();
// 改騎自行車
$person->setTravelStrategy(new BicycleStrategy());
$person->travel();
}
}
Person
是一個想要旅行的人,它持有一個具體的交通方式類$strategy
,最後旅遊就是委託給這個交通方式$strategy來完成的——是騎車還是自駕遊還是坐飛機。在使用時先new 一個對象,並且在構造器裏面注入一種交通方式初始化旅行的方式,並且我還可以通過Person::setTravelStrategy
臨時決定改變旅行方式——這是策略模式的應用場景。
我們看看這一行:
$person = new Person(new TrainStrategy());
這種寫法大家再也熟悉不過了吧?其實這完成了兩步操作:
實例化對象Person,方式是 new
注入外部實例new TrainStrategy()
並對$person
初始化。注入的可以是實例當然也可以是常量。
但是按照Yii2的風格,就應該是這樣的:
class Person extends Component
{
private $strategy = null;
/**
* 旅行.
*/
public function setTravelStrategy($travel)
{
if (!($travel instanceof TravelStrategy)) {
$travel = Yii::createObject($travel);
}
$this->strategy = $travel;
}
/**
* 旅行.
*/
public function travel()
{
return $this->strategy->travelAlgorithm();
}
}
用法就大概是這樣的風格:
//用配置創建對象並初始化,選擇火車出行
$person = Yii::createObject([
'class' => Person::class,
'travelStrategy' => [
'class' => TrainStrategy::class
]
]);
$person->travel();
//用配置重新初始化對象,改騎自行車
$person = Yii::configure($person, [
'travelStrategy' => [
'class' => BicycleStrategy::class
]
]);
$person->travel();
上面這個例子,應該可以幫助大家瞭解Yii2配置的作用和使用方式。其中創建對象的方式不是通過new關鍵詞,而是去依賴注入容器(DI Container)中去獲取的,後面我們會講到。
Yii2框架似乎不太喜歡用“通用”的實例化和初始化的方式,在Yii2框架內部幾乎都是通過配置來實現對象的實例化和初始化。這是Yii2的一個風格,當然這種風格看起來更爲簡潔(前提是你已經熟悉),使用起來則是更爲方便。雖說看起來有差異,但是本質上還是一樣的,只是注入的方式有一些差別罷了。
配置的格式
一個配置的格式可以描述爲以下形式:
[
'class' => 'ClassName',
'propertyName' => 'propertyValue',
'on eventName' => $eventHandler,
'as behaviorName' => $behaviorConfig,
]
其中,
- class 元素指定了將要創建的對象的完整類名(用Object::class就可以實現)
- propertyName 元素指定了對象可寫屬性的初始值
- on eventName 元素指定了附加到對象事件上的處理器。 請注意,數組的鍵名由 on 前綴加事件名組成。on和事件名之間只能有一個空格
- as behaviorName 元素指定了附加到對象的行爲。 請注意,數組的鍵名由 as 前綴加行爲名組成。as和行爲名之間只能有一個空格。
$behaviorConfig
值表示創建行爲的配置信息,格式與我們之前描述的配置格式一樣。
下面是一個配置了初始化屬性值,事件處理器和行爲的示例:
[
'class' => 'app\components\SearchEngine',
'apiKey' => 'xxxxxxxx',
'on search' => function ($event) {
Yii::info("搜索的關鍵詞: " . $event->keyword);
},
'as indexer' => [
'class' => 'app\components\IndexerBehavior',
// ... 初始化屬性值 ...
],
]
配置實現的原理
我們按照這樣的約定,就可以通過配置數組去是實例化和初始化對象:
實現Configurable接口,只要你繼承BaseObject或者Component,這條都是滿足的——無須擔心
子類重載__construct方法時,把配置數組放到構造器的最後一個參數:__construct($param1, $param2, ..., $config)
子類在自己的__construct
最後,必須調用parent::__construct($config)
方法
到底是如何實現的呢?這還得從BaseObject中說起,看看BaseObject的構造器:
public function __construct($config = [])
{
if (!empty($config)) {
Yii::configure($this, $config);
}
$this->init();
}
我們知道Yii::configure
是實現配置的。我們如果每個子類的__construct
都按照上面的規範寫,那麼到最後無異會調用BaseObject::__construct
,並且將子類的配置數組$config
也傳遞過來,最終被Yii::configure
使用。我們再看看這個方法:
// $object就是即將被配置的對象實例,$properties是配置數組
public static function configure($object, $properties)
{
//遍歷每個參數,將其設置爲屬性,這裏可能調用setter等方法
foreach ($properties as $name => $value) {
$object->$name = $value;
}
return $object;
}
這一句 name = $value可能會發生很多故事,可能會調用Component::__setter
或者BaseObject::__setter
(參看我們前面講屬性,行爲,事件的章節)
配置的應用
Yii 中的配置可以用在很多場景,除了我們上面舉的例子,最常見的莫過於Yii最大的實例Application的配置了。Application堪稱最複雜的配置之一了, 因爲 Application 類擁有很多可配置的屬性和事件。 更重要的是它的 yii\web\Application::components 屬性也可以接收配置數組並通過應用註冊爲組件,配置中還可以有配置。 以下是一個針對基礎應用模板的應用配置概要:
$config = [
'id' => 'basic',
'basePath' => dirname(__DIR__),
'extensions' => require(__DIR__ . '/../vendor/yiisoft/extensions.php'),
'components' => [
'cache' => [
'class' => 'yii\caching\FileCache',
],
'mailer' => [
'class' => 'yii\swiftmailer\Mailer',
],
'log' => [
'class' => 'yii\log\Dispatcher',
'traceLevel' => YII_DEBUG ? 3 : 0,
'targets' => [
[
'class' => 'yii\log\FileTarget',
],
],
],
'db' => [
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=localhost;dbname=stay2',
'username' => 'root',
'password' => '',
'charset' => 'utf8',
],
],
];
配置中沒有 class 鍵的原因是這段配置應用在下面的入口腳本中, 類名已經指定了。
(new yii\web\Application($config))->run();
Application的配置中,比較重要的是components屬性的配置了。在components裏配置了的,都作爲一個單例可以通過Yii::$app->component
來訪問;
另外,自版本 2.0.11 開始,系統配置支持使用 container 屬性來配置依賴注入容器 例如:
$config = [
'id' => 'basic',
'basePath' => dirname(__DIR__),
'extensions' => require __DIR__ . '/../vendor/yiisoft/extensions.php',
'container' => [
'definitions' => [
'yii\widgets\LinkPager' => ['maxButtonCount' => 5]
],
'singletons' => [
// 依賴注入容器單例配置
]
]
];
我們這裏重點闡述的是配置的原理,並不對Application做過多的配置,只是加深下大家對配置用法的印象而已,關於Application的配置我們以後會有講到。
配置文件
當配置的內容十分複雜,通用做法是將其存儲在一或多個 PHP 文件中, 這些文件被稱爲配置文件。一個配置文件返回的是 PHP 數組。 例如,像這樣把應用配置信息存儲在名爲 web.php 的文件中:
return [
'id' => 'basic',
'basePath' => dirname(__DIR__),
'extensions' => require(__DIR__ . '/../vendor/yiisoft/extensions.php'),
'components' => require(__DIR__ . '/components.php'),
];
鑑於 components 配置也很複雜,上述代碼把它們存儲在單獨的 components.php 文件中,並且包含在 web.php 裏。 components.php 的內容如下:
return [
'cache' => [
'class' => 'yii\caching\FileCache',
],
'mailer' => [
'class' => 'yii\swiftmailer\Mailer',
],
'log' => [
'class' => 'yii\log\Dispatcher',
'traceLevel' => YII_DEBUG ? 3 : 0,
'targets' => [
[
'class' => 'yii\log\FileTarget',
],
],
],
'db' => [
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=localhost;dbname=stay2',
'username' => 'root',
'password' => '',
'charset' => 'utf8',
],
];
如果數據庫配置複雜了,你也可以單獨拿出來——總之,簡潔易維護就行。
僅僅需要 “require”,就可以取得一個配置文件的配置內容,像這樣:
$config = require('path/to/web.php');
(new yii\web\Application($config))->run();
默認配置
Yii::createObject()
方法基於依賴注入容器實現,你可以通過Yii::creatObject()
創建對象時實現配置,同樣也可以直接調用Yii::$container->set()
來實現:
\Yii::$container->set('yii\widgets\LinkPager', [
'maxButtonCount' => 5,
]);
環境常量
配置經常會隨着環境的更改而更改,有哪些環境呢?——生產,開發,測試。不同的環境可能會提供不同的組件,因此我們可以先定義不同的環境變量。
爲了便於切換使用環境,Yii 提供了一個定義在入口腳本中的 YII_ENV 常量。 如下:
defined('YII_ENV') or define('YII_ENV', 'dev');
你可以把 YII_ENV 定義成以下任何一種值:
- prod:生產環境。常量 YII_ENV_PROD 將被看作 true,這是 YII_ENV 的默認值。
- dev:開發環境。常量 YII_ENV_DEV 將被看作 true。
- test:測試環境。常量 YII_ENV_TEST 將被看作 true。
有了這些環境常量,你就可以根據當下應用運行環境的不同,進行差異化配置。 例如,應用可以包含下述代碼只在開發環境中開啓 調試工具。
$config = [...];
if (YII_ENV_DEV) {
// 根據 `dev` 環境進行的配置調整
$config['bootstrap'][] = 'debug';
$config['modules']['debug'] = 'yii\debug\Module';
}
return $config;