Yii2源码分析(二):配置解析

1 $config = require(__DIR__ . '/../config/web.php'); // 加载配置文件
2 (new yii\web\Application($config))->run();

上一篇文章分析了大部分yii入口文件的内容,剩下这最后两行代码再本篇中继续分析,至于原因是因为这里涉及到yii2的核心类Application和配置的解析比较长。

Yii2源码分析(二):配置解析_第1张图片

上图是web/Application的继承链,我们先分析(new Application),来看到Appplication类的的构造函数:

yii\base\Applicaiton:

 1 public function __construct($config = [])
 2 {
 3     // 把当前实例(yii\web\Application)存到全局变量$app里,单例模式,节省开销。
 4     Yii::$app = $this;
 5     static::setInstance($this);
 6     // 标注当前的实名周期:0 开始
 7     $this->state = self::STATE_BEGIN;
 8 
 9     $this->preInit($config);
10 
11     $this->registerErrorHandler($config);
12 
13     Component::__construct($config);

设置实例的缓存

下面开始逐个分析,首先是 static::setInstance($this)

yii\base\Module

 1 public static function setInstance($instance)
 2 {
 3     // 如果$instance不是实例就删除
 4     if ($instance === null) {
 5         unset(Yii::$app->loadedModules[get_called_class()]);
 6     } else {
 7         // 存储当前实例化的实例
 8         Yii::$app->loadedModules[get_class($instance)] = $instance;
 9     }
10 }

这边涉及到php后期静态绑定的概念,是为了确保获取的是当前调用类的类名,这边这么做我个人的理解是为了缓存实例。

预初始化应用

接下来看$this->preInit($config), 用来检查config数组是否缺少必须的参数,然后根据参数预设一些属性。

 1 public function preInit(&$config)
 2 {
 3     // 检查是否有id, 没有就抛出异常中止执行
 4     if (!isset($config['id'])) {
 5         throw new InvalidConfigException('The "id" configuration for the Application is required.');
 6     }
 7     if (isset($config['basePath'])) {
 8         // 调用setter函数设置basePath属性
 9         $this->setBasePath($config['basePath']);
10         unset($config['basePath']);
11     } else {
12         throw new InvalidConfigException('The "basePath" configuration for the Application is required.');
13     }
14     // 设置vendorPath(composer扩展路径)的路径别名@vendor
15     if (isset($config['vendorPath'])) {
16         $this->setVendorPath($config['vendorPath']);
17         unset($config['vendorPath']);
18     } else {
19         // set "@vendor,@bower,@npm" 
20         $this->getVendorPath();
21     }
22     // 设置runtime的路径别名@runtime(用来放日志文件缓存等临时文件的目录)
23     if (isset($config['runtimePath'])) {
24         $this->setRuntimePath($config['runtimePath']);
25         unset($config['runtimePath']);
26     } else {
27         // set "@runtime"
28         $this->getRuntimePath();
29     }
30     // 设置时区 检查config, php.ini的时区设置, 默认设置UTC
31     if (isset($config['timeZone'])) {
32         $this->setTimeZone($config['timeZone']);
33         unset($config['timeZone']);
34     } elseif (!ini_get('date.timezone')) {
35         $this->setTimeZone('UTC');
36     }
37     // 设置自定义DI容器
38     if (isset($config['container'])) {
39         $this->setContainer($config['container']);
40 
41         unset($config['container']);
42     }
43     // 把预设组件的配置和config内自定义的配置传给对应class, 这里叫传合适点,因为合并不在这一步。
44     // merge core components with custom components
45     foreach ($this->coreComponents() as $id => $component) {
46         if (!isset($config['components'][$id])) {
47             $config['components'][$id] = $component;
48         } elseif (is_array($config['components'][$id]) && !isset($config['components'][$id]['class'])) {
49             // 把class补上,所以这些核心组件的config可以缺省class
50             $config['components'][$id]['class'] = $component['class'];
51         }
52     }
53 }

简单总结下这个函数的功能:

1. 检查config的参数,设置默认配置

2. 设置路径别名

@yii (预设的)
下面是preinit里设置的:
@app(等于basePath)
setVendorPath时候设置
@vendor
@bower
@npm
setRuntimePath设置
@runtime

 

3. 加载coreComponents,就是一些预设的组件,如下:

base\Application

 1     /**
 2      * Returns the configuration of core application components.
 3      * @see set()
 4      */
 5     public function coreComponents()
 6     {
 7         return [
 8             'log' => ['class' => 'yii\log\Dispatcher'],
 9             'view' => ['class' => 'yii\web\View'],
10             'formatter' => ['class' => 'yii\i18n\Formatter'],
11             'i18n' => ['class' => 'yii\i18n\I18N'],
12             'mailer' => ['class' => 'yii\swiftmailer\Mailer'],
13             'urlManager' => ['class' => 'yii\web\UrlManager'],
14             'assetManager' => ['class' => 'yii\web\AssetManager'],
15             'security' => ['class' => 'yii\base\Security'],
16         ];
17     }

web\application

 1     /**
 2      * {@inheritdoc}
 3      */
 4     public function coreComponents()
 5     {
 6         return array_merge(parent::coreComponents(), [
 7             'request' => ['class' => 'yii\web\Request'],
 8             'response' => ['class' => 'yii\web\Response'],
 9             'session' => ['class' => 'yii\web\Session'],
10             'user' => ['class' => 'yii\web\User'],
11             'errorHandler' => ['class' => 'yii\web\ErrorHandler'],
12         ]);
13     }

 

注册错误处理函数

 1 protected function registerErrorHandler(&$config)
 2 {
 3     // BaseYii中声明了默认是true的常量
 4     if (YII_ENABLE_ERROR_HANDLER) {
 5         if (!isset($config['components']['errorHandler']['class'])) {
 6             echo "Error: no errorHandler component is configured.\n";
 7             exit(1);
 8         }
 9           // set错误处理函数
10         $this->set('errorHandler', $config['components']['errorHandler']);
11         unset($config['components']['errorHandler']);
12         // 调用上面注册的errorHanlder组件的register
13         $this->getErrorHandler()->register();
14     }
15 }

注册错误处理函数,主要依赖于这个原生函数(https://www.php.net/manual/zh/function.set-error-handler),Yii2帮我们做了很多兼容性处理。

组件初始化

base/Application

1 Component::__construct($config);

base/BaseObject (原来叫Object,为了兼容php 7.2增加了Object保留类后改名)

16     public function __construct($config = [])
17     {
18         if (!empty($config)) {
19             Yii::configure($this, $config);
20         }
21         $this->init();
22     }

实际调用了BaseObject的构造函数,根据文章开头的继承链来看相当于跳过了Module的构造,个人理解application是特殊的module,存储在Yii::$app里全局调用,再给这个Module定义id意义不大。

base/Module

1     public function __construct($id, $parent = null, $config = [])
2     {
3         $this->id = $id;
4         $this->module = $parent;
5         parent::__construct($config);
6     }

接着回来看构造函数,调用了下面的函数,这边涉及到Yii2的一个特性 property(属性),相当于java的getter和setter,这边是将配置数组中的配置设置到对象实例的属性中。

1     public static function configure($object, $properties)
2     {
3         foreach ($properties as $name => $value) {
4             $object->$name = $value;
5         }
6 
7         return $object;
8     }

   然后会调用$this->init()函数初始化, web\Application没有重载这个函数,所以调用的是base\Application的函数。

base\Application

1 public function init()
2 {
3     // 设置生命周期
4     $this->state = self::STATE_INIT;
5     $this->bootstrap();
6 }

主要看下$this->boostrap()这个函数,这个函数涉及到两个地方:

 1     protected function bootstrap()
 2     {
 3         if ($this->extensions === null) {
 4             $file = Yii::getAlias('@vendor/yiisoft/extensions.php');
 5             $this->extensions = is_file($file) ? include $file : [];
 6         }
 7         foreach ($this->extensions as $extension) {
 8             if (!empty($extension['alias'])) {
 9                 foreach ($extension['alias'] as $name => $path) {
10                     Yii::setAlias($name, $path);
11                 }
12             }
13             if (isset($extension['bootstrap'])) {
14                 $component = Yii::createObject($extension['bootstrap']);
15                 if ($component instanceof BootstrapInterface) {
16                     Yii::debug('Bootstrap with ' . get_class($component) . '::bootstrap()', __METHOD__);
17                     $component->bootstrap($this);
18                 } else {
19                     Yii::debug('Bootstrap with ' . get_class($component), __METHOD__);
20                 }
21             }
22         }
23 
24         foreach ($this->bootstrap as $mixed) {
25             $component = null;
26             if ($mixed instanceof \Closure) {
27                 Yii::debug('Bootstrap with Closure', __METHOD__);
28                 if (!$component = call_user_func($mixed, $this)) {
29                     continue;
30                 }
31             } elseif (is_string($mixed)) {
32                 if ($this->has($mixed)) {
33                     $component = $this->get($mixed);
34                 } elseif ($this->hasModule($mixed)) {
35                     $component = $this->getModule($mixed);
36                 } elseif (strpos($mixed, '\\') === false) {
37                     throw new InvalidConfigException("Unknown bootstrapping component ID: $mixed");
38                 }
39             }
40 
41             if (!isset($component)) {
42                 $component = Yii::createObject($mixed);
43             }
44 
45             if ($component instanceof BootstrapInterface) {
46                 Yii::debug('Bootstrap with ' . get_class($component) . '::bootstrap()', __METHOD__);
47                 $component->bootstrap($this);
48             } else {
49                 Yii::debug('Bootstrap with ' . get_class($component), __METHOD__);
50             }
51         }
52     }

这段函数比较长,分成两个部分,主要功能都是加载扩展,上半部分初始化了定义在extensions.php里的扩展,下半部分启动了再配置文件里定义的预加载组件。

 

到此(new Application)调用的作用简单说明完了,application的参数预处理做完了,下面开始正式启动。

应用启动

base/Application

 1 public function run()
 2 {
 3     try {
 4     
 5         $this->state = self::STATE_BEFORE_REQUEST;
 6         $this->trigger(self::EVENT_BEFORE_REQUEST);
 7 
 8         $this->state = self::STATE_HANDLING_REQUEST;
 9         $response = $this->handleRequest($this->getRequest());
10 
11         $this->state = self::STATE_AFTER_REQUEST;
12         $this->trigger(self::EVENT_AFTER_REQUEST);
13 
14         $this->state = self::STATE_SENDING_RESPONSE;
15         $response->send();
16 
17         $this->state = self::STATE_END;
18 
19         return $response->exitStatus;
20 
21     } catch (ExitException $e) {
22 
23         $this->end($e->statusCode, isset($response) ? $response : null);
24         return $e->statusCode;
25 
26     }
27 }

   启动web/Application就开始正式处理请求了,这里涉及yii2的event,触发了几个预设的hook。

你可能感兴趣的:(Yii2源码分析(二):配置解析)