1 $config = require(__DIR__ . '/../config/web.php'); // 加载配置文件 2 (new yii\web\Application($config))->run();
上一篇文章分析了大部分yii入口文件的内容,剩下这最后两行代码再本篇中继续分析,至于原因是因为这里涉及到yii2的核心类Application和配置的解析比较长。
上图是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。