理解Yii2核心架构
Yii2是一个设计简单,灵活性高,容易上手的MVC框架。它的生态所有欠缺,所以很需要理解框架,然后DIY。很希望Yii3能早日发布。
使用Yii2也有了几年,在理解控制反转/IoC
的概念之后,再结合框架yii\base\BaseObject
和yii\base\Component
的源码解读,对框架又有了更深清晰的认识。
首先,不必太纠结于程序设计的各种概念,比如这里的IoC。因为在编写代码的过程中,可能已经在无意中已经用到了相关思想。这些概念只是对编写的代码做了抽象的总结。但是呢,理解设计概念,更容易理解大块的代码逻辑。
控制反转/Inversion of Control(IoC)
我对控制反转的理解是:一个对象所需的依赖不由自己内部主动创建,而由外部提供。这个由内而外的变化,就是反转。发生发转的是所需依赖的获取过程。用以下简单的代码示例来辅助理解。
interface Hello
{
public function hello();
}
class A implements Hello
{
public function hello() {
echo 'a';
}
}
class B implements Hello
{
public function hello()
{
echo 'b';
}
}
// 常规方式(未采用控制反转设计)
class H
{
protected $obj;
public function __construct()
{
$this->obj = new A(); // 所需依赖由H类自己创建
}
public function run() {
$this->obj->hello();
}
}
// 控制反转-依赖注入方式设计
class I
{
protected $obj;
public function __construct(Hello $obj)
{
// 所需依赖由外部提供
$this->obj = $obj;
}
public function run()
{
$this->obj->hello();
}
}
由上可见,不管给I
类提供A
或B
类的对象,甚至其他Hello
接口的实现,都能正常工作。而H
类与A
类强耦合,无法直接替换依赖。代入一个简单场景进行理解:H
和I
是用来给用户发通知的,有A
和B
两种通知方式(如:QQ、微信)。H
就只能通过A
方式来发送,I
却可以灵活地使用多种方式。I
类的代码逻辑并没有很复杂,却是通过依赖注入方式实现控制反转的典型方式。因此我才觉得无需太纠结于相关概念。
依赖注入的时候需要外部提供依赖,依赖注入容器/Dependency Injection Container
往往是提供所需依赖项的角色,它定义了各依赖项的实现。以下是一个简单的依赖注入容器示例。
class DiContainer
{
protected $definitions = [];
public function set($id, $def)
{
$this->definitions[$id] = $def;
}
public function get($id, $params = [])
{
if (!isset($this->definitions[$id])) {
throw new Exception("$id not defined");
}
$def = $this->definitions[$id];
return new $def(...$params);
}
}
结合上文的示例,使用场景演示代码
// 应用启动时
$container = new DiContainer(); // 往往是个单例
$container->set(Hello::class, A::class);
$container->set('my-id', I::class);
// 应用运行时
$helloObj = $container->get(Hello::class);
$myObj = $container->get('my-id', [$helloObj]);
$myObj->run();
其实,当DiContainer
使用PHP的反射特性时,根据I
类的构造参数,主动解析出一个Hello
实例,进一步实例化出my-id
的对象。此时,就不需要我们显式地获取Hello
对应实例,然后传递给my-id
的构造函数了。
Yii2中的依赖注入容器是yii\di\Container
,它的set
和get
方法更加强大,支持字符串、数组、闭包等多种定义方式,并能递归解析各个对象的依赖项。Yii::createObject
是对yii\di\Container
的封装。
此外,Yii2中使用yii\di\ServiceLocator
对依赖注入容器进一步封装,扩展了组件了定义功能。Yii2的yii\base\Application
继承自yii\base\Module
模块,是yii\di\ServiceLocator
的子类。得益于此,通过相关web.php
等配置文件,即可定义系统组件。
BaseObject和Component
Yii2框架几乎所有类都是BaseObject
的子类,它通过__set
、__get
等魔术方法,扩展了类属性的赋值和获取。同时实现了yii\base\Configurable
接口(虽然是空接口),使类获得了强大的配置能力,可以通过Yii::configure
填充类属性。
Component
是BaseObject
的子类,增加了事件
的定义和触发逻辑,并引入了行为
功能(是一种动态的混入,扩展了原类的功能)。并扩充了__set
、__get
、__call
等魔术方法,使其能够把行为
所具有的属性、方法作为自身的接口对外服务。
示例代码
class RunComponent extends yii\base\Component {
const AFTER_RUN = 'afterRun';
public function run()
{
$this->trigger(static::AFTER_RUN);
}
}
class HiBehavior extends yii\base\Behavior {
public $message = 'hi';
public function events()
{
return [
RunComponent::AFTER_RUN => 'bye',
];
}
public function hi()
{
echo $this->message;
}
public function bye()
{
echo 886;
}
public function getMsg()
{
return $this->message;
}
public function setMsg($value)
{
$this->message = $value;
}
}
$runner = Yii::createObject([
'class' => RunComponent::class,
'as hi' => [
'class' => HiBehavior::class,
'msg' => 'hello',
],
'on afterRun' => function () {
echo 'event';
},
]);
echo $runner->message; // hello
echo $runner->msg; // hello
$runner->msg = 'hi';
$runner->hi(); // hi
$runner->run(); // 886 event
$runner->bye(); // 886
理解了以上内容后,回头再看web.php
、console.php
里的内容,茅塞顿开,神清气爽。这个配置文件配置的是Application
类的属性,其中components
属性又是各个组件的定义和属性配置。从此,新世界的门大开。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战