在Yii中创建新对象或者初始化已经存在的对象广泛的使用配置,配置通常包含被创建对象的类名和一组将要赋值给对象的属性的初始值,这里的属性是Yii2的属性。还可以在对象的事件上绑定事件处理器,或者将行为附加到对象上。从而在定义了对象的初始值的同时,充分规定对象的运行时的动态特性。
以下代码中的配置被用来创建并初始化一个数据库连接:
$config = [
'class' => 'yiidbConnection',
'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' => 'appcomponentsSearchEngine',
'apiKey' => 'xxxxxxxx',
'on search' => function ($event) {
Yii::info("搜索的关键词: " . $event->keyword);
},
'as indexer' => [
'class' => 'appcomponentsIndexerBehavior',
// ... 初始化属性值 ...
],
]
配置实现的原理
我们按照这样的约定,就可以通过配置数组去是实例化和初始化对象:
- 实现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;
}
这一句$object->$name = $value
可能会发生很多故事,可能会调用Component::__setter
或者BaseObject::__setter
(参看我们前面讲属性,行为,事件的章节)
配置的应用
Yii 中的配置可以用在很多场景,除了我们上面举的例子,最常见的莫过于Yii最大的实例Application的配置了。Application堪称最复杂的配置之一了, 因为 Application 类拥有很多可配置的属性和事件。 更重要的是它的 yiiwebApplication::components 属性也可以接收配置数组并通过应用注册为组件,配置中还可以有配置。 以下是一个针对基础应用模板的应用配置概要:
$config = [
'id' => 'basic',
'basePath' => dirname(__DIR__),
'extensions' => require(__DIR__ . '/../vendor/yiisoft/extensions.php'),
'components' => [
'cache' => [
'class' => 'yiicachingFileCache',
],
'mailer' => [
'class' => 'yiiswiftmailerMailer',
],
'log' => [
'class' => 'yiilogDispatcher',
'traceLevel' => YII_DEBUG ? 3 : 0,
'targets' => [
[
'class' => 'yiilogFileTarget',
],
],
],
'db' => [
'class' => 'yiidbConnection',
'dsn' => 'mysql:host=localhost;dbname=stay2',
'username' => 'root',
'password' => '',
'charset' => 'utf8',
],
],
];
配置中没有 class 键的原因是这段配置应用在下面的入口脚本中, 类名已经指定了。
(new yiiwebApplication($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' => [
'yiiwidgetsLinkPager' => ['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' => 'yiicachingFileCache',
],
'mailer' => [
'class' => 'yiiswiftmailerMailer',
],
'log' => [
'class' => 'yiilogDispatcher',
'traceLevel' => YII_DEBUG ? 3 : 0,
'targets' => [
[
'class' => 'yiilogFileTarget',
],
],
],
'db' => [
'class' => 'yiidbConnection',
'dsn' => 'mysql:host=localhost;dbname=stay2',
'username' => 'root',
'password' => '',
'charset' => 'utf8',
],
];
如果数据库配置复杂了,你也可以单独拿出来——总之,简洁易维护就行。
仅仅需要 “require”,就可以取得一个配置文件的配置内容,像这样:
$config = require('path/to/web.php');
(new yiiwebApplication($config))->run();
默认配置
Yii::createObject() 方法基于依赖注入容器实现,你可以通过 Yii::creatObject() 创建对象时实现配置,同样也可以直接调用 Yii::$container->set() 来实现:
Yii::$container->set('yiiwidgetsLinkPager', [
'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'] = 'yiidebugModule';
}
return $config;
关于配置的东西,大概就是这么多了。