/ yii2  

Yii2 源码阅读 04 - Application

书接上文 yii\web\Application 类的层级结构:

yii\base\Configurable
|--- yii\base\BaseObject
|--- yii\base\Component
|--- yii\di\ServiceLocator
|--- yii\base\Module
|--- yii\base\Application
|--- yii\web\Application

yii\base\Application

Application is the base class for all application classes.

参见:应用(Applications)

路径属性:

  • @property string $basePath The root directory of the application.
  • @property string $runtimePath The directory that stores runtime files. Defaults to the “runtime” subdirectory under basePath.
  • @property string $vendorPath The directory that stores vendor files. Defaults to “vendor” directory under basePath.

配置属性:

  • @property-write array $container Values given in terms of name-value pairs. This property is write-only.
  • @property string $timeZone The time zone used by this application.
  • @property-read string $uniqueId The unique ID of the module.

组件属性:

  • \yii\web\AssetManager $assetManager
  • \yii\rbac\ManagerInterface $authManager
  • \yii\caching\CacheInterface $cache
  • \yii\db\Connection $db
  • \yii\web\ErrorHandler|\yii\console\ErrorHandler $errorHandler
  • \yii\i18n\Formatter $formatter
  • \yii\i18n\I18N $i18n
  • \yii\log\Dispatcher $log
  • \yii\mail\MailerInterface $mailer
  • \yii\web\Request|\yii\console\Request $request
  • \yii\web\Response|\yii\console\Response $response
  • \yii\base\Security $security
  • \yii\web\UrlManager $urlManager
  • View|\yii\web\View $view

$state:

  • STATE_BEGIN 0
  • STATE_INIT 1
  • STATE_BEFORE_REQUEST 2
  • STATE_HANDLING_REQUEST 3
  • STATE_AFTER_REQUEST 4
  • STATE_SENDING_RESPONSE 5
  • STATE_END 6

构造函数

/**
* Constructor.
* @param array $config name-value pairs that will be used to initialize the object properties.
* Note that the configuration must contain both [[id]] and [[basePath]].
* @throws InvalidConfigException if either [[id]] or [[basePath]] configuration is missing.
*/
public function __construct($config = [])
{
// 绑定 app
Yii::$app = $this;
// 设置当前请求这个 module class 的实例。
static::setInstance($this);
// 设置状态为 begin 0
$this->state = self::STATE_BEGIN;
// 验证 id basePath 必填,设置配置,注册核心 components
$this->preInit($config);
// 注册异常处理
$this->registerErrorHandler($config);
// BaseObject construct
Component::__construct($config);
}

/**
* 设置当前请求这个 module class 的实例。
* @param Module|null $instance the currently requested instance of this module class.
* If it is `null`, the instance of the calling class will be removed, if any.
*/
public static function setInstance($instance)
{
if ($instance === null) {
unset(Yii::$app->loadedModules[get_called_class()]);
} else {
// loadedModules: list of loaded modules indexed by their class names.
Yii::$app->loadedModules[get_class($instance)] = $instance;
}
}

/**
* Pre-initializes the application.
* 调用此方法开始的 application 的构造函数。
* 它初始化一些重要的 application properties。
* 如果你重写这个方法,请确保你调用父实现。
* @param array $config the application configuration
* @throws InvalidConfigException if either [[id]] or [[basePath]] configuration is missing.
*/
public function preInit(&$config)
{
// id 必填
if (!isset($config['id'])) {
throw new InvalidConfigException('The "id" configuration for the Application is required.');
}
// basePath 必填
if (isset($config['basePath'])) {
$this->setBasePath($config['basePath']);
unset($config['basePath']);
} else {
throw new InvalidConfigException('The "basePath" configuration for the Application is required.');
}

if (isset($config['vendorPath'])) {
$this->setVendorPath($config['vendorPath']);
unset($config['vendorPath']);
} else {
// set "@vendor"
$this->getVendorPath();
}
if (isset($config['runtimePath'])) {
$this->setRuntimePath($config['runtimePath']);
unset($config['runtimePath']);
} else {
// set "@runtime"
$this->getRuntimePath();
}

if (isset($config['timeZone'])) {
$this->setTimeZone($config['timeZone']);
unset($config['timeZone']);
} elseif (!ini_get('date.timezone')) {
$this->setTimeZone('UTC');
}

if (isset($config['container'])) {
$this->setContainer($config['container']);
unset($config['container']);
}

// merge core components with custom components
// coreComponents:
// [
// 'log' => ['class' => 'yii\log\Dispatcher'],
// 'view' => ['class' => 'yii\web\View'],
// 'formatter' => ['class' => 'yii\i18n\Formatter'],
// 'i18n' => ['class' => 'yii\i18n\I18N'],
// 'mailer' => ['class' => 'yii\swiftmailer\Mailer'],
// 'urlManager' => ['class' => 'yii\web\UrlManager'],
// 'assetManager' => ['class' => 'yii\web\AssetManager'],
// 'security' => ['class' => 'yii\base\Security'],
// ];
foreach ($this->coreComponents() as $id => $component) {
if (!isset($config['components'][$id])) {
$config['components'][$id] = $component;
} elseif (is_array($config['components'][$id]) && !isset($config['components'][$id]['class'])) {
$config['components'][$id]['class'] = $component['class'];
}
}
}

/**
* 注册 errorHandler 组件作为一个 PHP 错误处理程序。
* @param array $config application config
*/
protected function registerErrorHandler(&$config)
{
// 在 BaseYii 中 默认是 true
if (YII_ENABLE_ERROR_HANDLER) {
if (!isset($config['components']['errorHandler']['class'])) {
echo "Error: no errorHandler component is configured.\n";
exit(1);
}
$this->set('errorHandler', $config['components']['errorHandler']);
unset($config['components']['errorHandler']);
// \yii\web\ErrorHandler|\yii\console\ErrorHandler
//
$this->getErrorHandler()->register();
}
}

异常处理 ErrorHandler:

/**
* Register this error handler.
* @since 2.0.32 this will not do anything if the error handler was already registered
*/
public function register()
{
if (!$this->_registered) {
ini_set('display_errors', false);
set_exception_handler([$this, 'handleException']);
if (defined('HHVM_VERSION')) {
set_error_handler([$this, 'handleHhvmError']);
} else {
set_error_handler([$this, 'handleError']);
}
if ($this->memoryReserveSize > 0) {
$this->_memoryReserve = str_repeat('x', $this->memoryReserveSize);
}
register_shutdown_function([$this, 'handleFatalError']);
$this->_registered = true;
}
}

init

/**
* {@inheritdoc}
*/
public function init()
{
$this->state = self::STATE_INIT;
$this->bootstrap();
}

/**
* 初始化扩展并执行引导组件。
* This method is called by [[init()]] after the application has been fully configured.
* If you override this method, make sure you also call the parent implementation.
*/
protected function bootstrap()
{
// 注册绑定 extensions
if ($this->extensions === null) {
// @vendor/yiisoft/extensions.php:
// return array (
// 'yiisoft/yii2-bootstrap4' =>
// array (
// 'name' => 'yiisoft/yii2-bootstrap4',
// 'version' => '2.0.10.0',
// 'alias' =>
// array (
// '@yii/bootstrap4' => $vendorDir . '/yiisoft/yii2-bootstrap4/src',
// ),
// ),
// 'yiisoft/yii2-faker' =>
// array (
// 'name' => 'yiisoft/yii2-faker',
// 'version' => '2.0.5.0',
// 'alias' =>
// array (
// '@yii/faker' => $vendorDir . '/yiisoft/yii2-faker/src',
// ),
// ),
// ...
// );
$file = Yii::getAlias('@vendor/yiisoft/extensions.php');
$this->extensions = is_file($file) ? include $file : [];
}
foreach ($this->extensions as $extension) {
if (!empty($extension['alias'])) {
foreach ($extension['alias'] as $name => $path) {
Yii::setAlias($name, $path);
}
}
if (isset($extension['bootstrap'])) {
$component = Yii::createObject($extension['bootstrap']);
// 此 $component 如果实现了 BootstrapInterface 执行 bootstrap 方法
if ($component instanceof BootstrapInterface) {
Yii::debug('Bootstrap with ' . get_class($component) . '::bootstrap()', __METHOD__);
$component->bootstrap($this);
} else {
Yii::debug('Bootstrap with ' . get_class($component), __METHOD__);
}
}
}

// $this->bootstrap: list of components that should be run during the application [[bootstrap()|bootstrapping process]].
// 允许你用数组指定启动阶段 bootstrapping process 需要运行的组件。
//
// configuration adjustments for 'dev' environment
// $config['bootstrap'][] = 'debug';
// $config['modules']['debug'] = 'yii\debug\Module';
//
// 注意:启动太多的组件会降低系统性能,因为每次请求都需要重新运行启动组件, 因此谨慎配置启动组件。
foreach ($this->bootstrap as $mixed) {
$component = null;
if ($mixed instanceof \Closure) {
Yii::debug('Bootstrap with Closure', __METHOD__);
if (!$component = call_user_func($mixed, $this)) {
continue;
}
} elseif (is_string($mixed)) {
if ($this->has($mixed)) {
$component = $this->get($mixed);
} elseif ($this->hasModule($mixed)) {
$component = $this->getModule($mixed);
} elseif (strpos($mixed, '\\') === false) {
throw new InvalidConfigException("Unknown bootstrapping component ID: $mixed");
}
}

if (!isset($component)) {
$component = Yii::createObject($mixed);
}

// 此 $component 如果实现了 BootstrapInterface 执行 bootstrap 方法
if ($component instanceof BootstrapInterface) {
Yii::debug('Bootstrap with ' . get_class($component) . '::bootstrap()', __METHOD__);
$component->bootstrap($this);
} else {
Yii::debug('Bootstrap with ' . get_class($component), __METHOD__);
}
}
}

run

/**
* Runs the application.
* This is the main entrance of an application.
* @return int the exit status (0 means normal, non-zero values mean abnormal)
*/
public function run()
{
try {
// 修改状态
$this->state = self::STATE_BEFORE_REQUEST;
// 执行 beforeRequest
$this->trigger(self::EVENT_BEFORE_REQUEST);

// 修改状态
$this->state = self::STATE_HANDLING_REQUEST;
// abstract: Handles the specified request.
$response = $this->handleRequest($this->getRequest());

// 修改状态
$this->state = self::STATE_AFTER_REQUEST;
// 执行 afterRequest
$this->trigger(self::EVENT_AFTER_REQUEST);

// 修改状态
$this->state = self::STATE_SENDING_RESPONSE;
// 发送 response
$response->send();

// 修改状态
$this->state = self::STATE_END;

// 返回 response 退出状态
return $response->exitStatus;
} catch (ExitException $e) {
$this->end($e->statusCode, isset($response) ? $response : null);
return $e->statusCode;
}
}

/**
* 终止 application.
* 该方法替换 `exit()` 函数,确保完成应用程序生命周期终止应用程序
* @param int $status 退出状态(0表示正常退出,其他表示异常退出)。
* @param Response $response the response to be sent. If not set, the default application [[response]] component will be used.
* @throws ExitException if the application is in testing mode
*/
public function end($status = 0, $response = null)
{
// 如果当前状态是 STATE_BEFORE_REQUEST | STATE_HANDLING_REQUEST
// 执行 STATE_AFTER_REQUEST
if ($this->state === self::STATE_BEFORE_REQUEST || $this->state === self::STATE_HANDLING_REQUEST) {
// 设置为 STATE_AFTER_REQUEST
$this->state = self::STATE_AFTER_REQUEST;
$this->trigger(self::EVENT_AFTER_REQUEST);
}

// 如果当前状态不是 STATE_SENDING_RESPONSE | STATE_END
if ($this->state !== self::STATE_SENDING_RESPONSE && $this->state !== self::STATE_END) {
// 设置为 STATE_END
$this->state = self::STATE_END;
$response = $response ?: $this->getResponse();
$response->send();
}

// 如果是 env test,抛出异常
if (YII_ENV_TEST) {
throw new ExitException($status);
}

exit($status);
}

yii\web\Application

Application is the base class for all web application classes.

属性:

  • @property-read ErrorHandler $errorHandler The error handler application component.
  • @property string $homeUrl The homepage URL.
  • @property-read Request $request The request component.
  • @property-read Response $response The response component.
  • @property-read Session $session The session component.
  • @property-read User $user The user component.

属性:

  • public $defaultRoute = 'site'; the default route of this application.
  • public $catchAll; 它指定一个要处理所有用户请求的 控制器方法, 通常在维护模式下使用,同一个方法处理所有用户请求。该配置为一个数组,第一项指定动作的路由,剩下的数组项 (key-value 成对) 指定传递给动作的参数。
  • public $controller; the currently active controller instance
[
'catchAll' => [
'offline/notice',
'param1' => 'value1',
'param2' => 'value2',
],
]

bootstrap

protected function bootstrap()
{
$request = $this->getRequest();
// /pc/site/yii-app/web
Yii::setAlias('@webroot', dirname($request->getScriptFile()));
// /
Yii::setAlias('@web', $request->getBaseUrl());

parent::bootstrap();
}

handleRequest

核心方法:

/**
* Handles the specified request.
*
* @param Request $request the request to be handled
*
* @return Response the resulting response
*
* @throws NotFoundHttpException if the requested route is invalid
*/
public function handleRequest($request)
{
// 获取 route params
if (empty($this->catchAll)) {
try {
list($route, $params) = $request->resolve();
} catch (UrlNormalizerRedirectException $e) {
$url = $e->url;
if (is_array($url)) {
if (isset($url[0])) {
// ensure the route is absolute
$url[0] = '/'.ltrim($url[0], '/');
}
$url += $request->getQueryParams();
}

return $this->getResponse()->redirect(Url::to($url, $e->scheme), $e->statusCode);
}
} else {
// 'catchAll' => [
// 'offline/notice',
// 'param1' => 'value1',
// 'param2' => 'value2',
// ],
// 约定 idx 0 是路由
$route = $this->catchAll[0];
$params = $this->catchAll;
unset($params[0]);
}

try {
Yii::debug("Route requested: '$route'", __METHOD__);
// 绑定路由
$this->requestedRoute = $route;
// 执行 Module->runAction
$result = $this->runAction($route, $params);
// 如果结果是 Response 则直接返回
if ($result instanceof Response) {
return $result;
}

// 否则获取 Response,将结果绑定到 $response->data
$response = $this->getResponse();
if (null !== $result) {
$response->data = $result;
}

return $response;
} catch (InvalidRouteException $e) {
throw new NotFoundHttpException(Yii::t('yii', 'Page not found.'), $e->getCode(), $e);
}
}

coreComponents

public function coreComponents()
{
return array_merge(parent::coreComponents(), [
'request' => ['class' => 'yii\web\Request'],
'response' => ['class' => 'yii\web\Response'],
'session' => ['class' => 'yii\web\Session'],
'user' => ['class' => 'yii\web\User'],
'errorHandler' => ['class' => 'yii\web\ErrorHandler'],
]);
}

– EOF –