用yii2框架用了将近2年,一直都没有去看过它底层源码, 马上快不用了,最近对其源码研究一番,哈哈
废话少说,上代码,
入口文件是web/index.php
<?php
defined('YII_DEBUG') or define('YII_DEBUG', true);
defined('YII_ENV') or define('YII_ENV', 'dev');
//这行我在composer autoload流程已经分析过
require __DIR__ . '/../vendor/autoload.php';
//见解释1-1
require __DIR__ . '/../vendor/yiisoft/yii2/Yii.php';
//配置文件
$config = require __DIR__ . '/../config/web.php';
//最关键的一点,见解释1-2
(new yii\web\Application($config))->run();
解释1-1
直接上Yii.php文件源码
<?php
require(__DIR__ . '/BaseYii.php');
class Yii extends \yii\BaseYii
{
}
//实际上调用的是BaseYii的autoload方法,自动加载yii的类
spl_autoload_register(['Yii', 'autoload'], true, true);
//yii类名和yii类名所在文件的映射数组
Yii::$classMap = require(__DIR__ . '/classes.php');
//依赖注入容器,这个后续文章再分析,先知道有这么一个东东
Yii::$container = new yii\di\Container();
解释1-2
我们最关键的点来了分析application启动流程
首先看看Application构造函数
首先进入yii\web\Application类,发现没有构造方法,于是跟踪它的层级关系,列出来:
yii\web\Application -> \yii\base\Application -> \yii\base\Module -> \yii\di\ServiceLocator -> \yii\base\Component
-> \yii\base\BaseObject -> \yii\base\Configurable(接口interface)
首先进入yii\base\Application找到__construct方法:
public function __construct($config = [])
{
//保存当前启动的application实例
Yii::$app = $this;
//将Yii::$app->loadedModules[实例类名] = 当前实例;
$this->setInstance($this);
$this->state = self::STATE_BEGIN;
//见解释1-2-1
$this->preInit($config);
//见解释1-2-2
$this->registerErrorHandler($config);
//见解释1-2-3
Component::__construct($config);
}
解释1-2-1:
/*
该函数作用是将配置数组进一步合并完善数组中的key
$config即为入口文件包含到的config/web.php返回的数组,举例如下:
$config = [
'id' => 'basic',
'basePath' => dirname(__DIR__),
'bootstrap' => ['log'],
'aliases' => [
'@bower' => '@vendor/bower-asset',
'@npm' => '@vendor/npm-asset',
],
'components' => [
'request' => [
// !!! insert a secret key in the following (if it is empty) - this is required by cookie validation
'cookieValidationKey' => '',
],
'cache' => [
'class' => 'yii\caching\FileCache',
],
'user' => [
'identityClass' => 'app\models\User',
'enableAutoLogin' => true,
],
'errorHandler' => [
'errorAction' => 'site/error',
],
'mailer' => [
'class' => 'yii\swiftmailer\Mailer',
'useFileTransport' => true,
],
'log' => [
'traceLevel' => YII_DEBUG ? 3 : 0,
'targets' => [
[
'class' => 'yii\log\FileTarget',
'levels' => ['error', 'warning'],
],
],
],
'db' => $db,
],
'params' => $params,
];
*/
public function preInit(&$config)
{
if (!isset($config['id'])) {
throw new InvalidConfigException('The "id" configuration for the Application is required.');
}
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 {
$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']);
}
/*
coreComponents返回核心组件
return [
'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'],
];
合并配置文件数组的components key内容
*/
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'];
}
}
}
解释1-2-2:
protected function registerErrorHandler(&$config)
{
//YII_ENABLE_ERROR_HANDLER可以在文件中配,默认为true
if (YII_ENABLE_ERROR_HANDLER) {
if (!isset($config['components']['errorHandler']['class'])) {
echo "Error: no errorHandler component is configured.\n";
exit(1);
}
/*
晒个默认配置
'errorHandler' => [
'errorAction' => 'site/error',
],
$this->set方法是引自\yii\di\ServiceLocator的set方法,
注册组件$this->_definitions['erroHandler'] = ['errorAction' => 'site/error','class'=>'yii\web\ErrorHandler'];
*/
$this->set('errorHandler', $config['components']['errorHandler']);
unset($config['components']['errorHandler']);
//这个方法会实例化errorHandler的class,实例化这步实际上用到依赖注入,之前我已经讲过一点,以后写个yii2创建对象流程
//并将实例化的对象保存到$this->__components['errorHandler']
$this->getErrorHandler()->register();
}
}
解释1-2-3:
//实际调用的是yii\base\BaseObject类的构造方法
public function __construct($config = [])
{
if (!empty($config)) {
//将$config数组中的每个key都赋值$this->本地化变量
Yii::configure($this, $config);
}
$this->init();
}
很明显追踪$this->init()方法,后面追踪到yii\base\Application的init方法。
public function init()
{
$this->state = self::STATE_INIT;
$this->bootstrap();
}
再看看bootstrap方法
先看看yii\web\Application的bootstrap方法
protected function bootstrap()
{
//获得request对象实例
$request = $this->getRequest();
Yii::setAlias('@webroot', dirname($request->getScriptFile()));
Yii::setAlias('@web', $request->getBaseUrl());
parent::bootstrap();
}
再看看yii\base\Application的bootstrap方法
protected function bootstrap()
{
if ($this->extensions === null) {
$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']);
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__);
}
}
}
//已配置需要初始化的组件初始化
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);
}
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__);
}
}
}
到此new Application($config)这一步分析完毕
再来看看$app->run()做了什么
先打开yii\base\Application的run方法
public function run()
{
try {
$this->state = self::STATE_BEFORE_REQUEST;
//这里可以绑定自定义事件,类似钩子
$this->trigger(self::EVENT_BEFORE_REQUEST);
$this->state = self::STATE_HANDLING_REQUEST;
//最重要的一点 见解释2-1
$response = $this->handleRequest($this->getRequest());
$this->state = self::STATE_AFTER_REQUEST;
$this->trigger(self::EVENT_AFTER_REQUEST);
$this->state = self::STATE_SENDING_RESPONSE;
//见解释2-2
$response->send();
$this->state = self::STATE_END;
return $response->exitStatus;
} catch (ExitException $e) {
$this->end($e->statusCode, isset($response) ? $response : null);
return $e->statusCode;
}
}
解释2-1:
打开yii\web\Application的handleRequest
//$request为yii\web\Request类的实例
public function handleRequest($request)
{
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 {
$route = $this->catchAll[0];
$params = $this->catchAll;
unset($params[0]);
}
try {
Yii::debug("Route requested: '$route'", __METHOD__);
$this->requestedRoute = $route;
/*
例如访问url为http://domain/web/index.php?r=post/index&id=3
$route为路由url字符串,得到post/index
$params为Query String数组,得到['id'=>3, 'r'=> 'post/index']
$result的值为对应conroller执行对应action返回的值或者对象
*/
$result = $this->runAction($route, $params);
if ($result instanceof Response) {
return $result;
}
//构造一个Response对象
$response = $this->getResponse();
if ($result !== null) {
$response->data = $result;
}
return $response;
} catch (InvalidRouteException $e) {
throw new NotFoundHttpException(Yii::t('yii', 'Page not found.'), $e->getCode(), $e);
}
}
我们进入$this->runAction看看
public function runAction($route, $params = [])
{
//得到($controller实例对象和action名称的字符串)
$parts = $this->createController($route);
if (is_array($parts)) {
/* @var $controller Controller */
list($controller, $actionID) = $parts;
$oldController = Yii::$app->controller;
Yii::$app->controller = $controller;
//执行controller的对应的actionID方法,该方法返回的内容赋值给$result
$result = $controller->runAction($actionID, $params);
if ($oldController !== null) {
Yii::$app->controller = $oldController;
}
return $result;
}
$id = $this->getUniqueId();
throw new InvalidRouteException('Unable to resolve the request "' . ($id === '' ? $route : $id . '/' . $route) . '".');
}
解释2-2:
打开yii\web\Response的send方法
public function send()
{
if ($this->isSent) {
return;
}
$this->trigger(self::EVENT_BEFORE_SEND);
//取得$response对象的format再获得该format对象的实例执行format方法(就是header设置Content-Type)
//见2-2-1
$this->prepare();
$this->trigger(self::EVENT_AFTER_PREPARE);
//见2-2-2
$this->sendHeaders();
//见2-2-3
$this->sendContent();
$this->trigger(self::EVENT_AFTER_SEND);
$this->isSent = true;
}
解释2-2-1:
protected function prepare()
{
if ($this->stream !== null) {
return;
}
if (isset($this->formatters[$this->format])) {
$formatter = $this->formatters[$this->format];
if (!is_object($formatter)) {
$this->formatters[$this->format] = $formatter = Yii::createObject($formatter);
}
if ($formatter instanceof ResponseFormatterInterface) {
$formatter->format($this);
} else {
throw new InvalidConfigException("The '{$this->format}' response formatter is invalid. It must implement the ResponseFormatterInterface.");
}
} elseif ($this->format === self::FORMAT_RAW) {
if ($this->data !== null) {
$this->content = $this->data;
}
} else {
throw new InvalidConfigException("Unsupported response format: {$this->format}");
}
if (is_array($this->content)) {
throw new InvalidArgumentException('Response content must not be an array.');
} elseif (is_object($this->content)) {
if (method_exists($this->content, '__toString')) {
$this->content = $this->content->__toString();
} else {
throw new InvalidArgumentException('Response content must be a string or an object implementing __toString().');
}
}
}
解释2-2-2:
protected function sendHeaders()
{
if (headers_sent($file, $line)) {
throw new HeadersAlreadySentException($file, $line);
}
if ($this->_headers) {
$headers = $this->getHeaders();
foreach ($headers as $name => $values) {
$name = str_replace(' ', '-', ucwords(str_replace('-', ' ', $name)));
// set replace for first occurrence of header but false afterwards to allow multiple
$replace = true;
foreach ($values as $value) {
header("$name: $value", $replace);
$replace = false;
}
}
}
$statusCode = $this->getStatusCode();
header("HTTP/{$this->version} {$statusCode} {$this->statusText}");
$this->sendCookies();
}
这里补充下sendCookies方法:
protected function sendCookies()
{
if ($this->_cookies === null) {
return;
}
$request = Yii::$app->getRequest();
if ($request->enableCookieValidation) {
if ($request->cookieValidationKey == '') {
throw new InvalidConfigException(get_class($request) . '::cookieValidationKey must be configured with a secret key.');
}
$validationKey = $request->cookieValidationKey;
}
foreach ($this->getCookies() as $cookie) {
$value = $cookie->value;
if ($cookie->expire != 1 && isset($validationKey)) {
$value = Yii::$app->getSecurity()->hashData(serialize([$cookie->name, $value]), $validationKey);
}
setcookie($cookie->name, $value, $cookie->expire, $cookie->path, $cookie->domain, $cookie->secure, $cookie->httpOnly);
}
}
解释2-2-3:
protected function sendContent()
{
if ($this->stream === null) {
echo $this->content;
return;
}
set_time_limit(0); // Reset time limit for big files
$chunkSize = 8 * 1024 * 1024; // 8MB per chunk
if (is_array($this->stream)) {
list($handle, $begin, $end) = $this->stream;
fseek($handle, $begin);
while (!feof($handle) && ($pos = ftell($handle)) <= $end) {
if ($pos + $chunkSize > $end) {
$chunkSize = $end - $pos + 1;
}
echo fread($handle, $chunkSize);
flush(); // Free up memory. Otherwise large files will trigger PHP's memory limit.
}
fclose($handle);
} else {
while (!feof($this->stream)) {
echo fread($this->stream, $chunkSize);
flush();
}
fclose($this->stream);
}
}
至此源码整个流程分析基本完毕,有些地方可能分析不够详细,后续再详细补充。
最后附加下官网文档的部分内容帮助大家理解
以下图表展示了一个应用如何处理请求:
用户向入口脚本 web/index.php
发起请求。
入口脚本加载应用配置并创建一个应用 实例去处理请求。
应用通过请求组件解析请求的 路由。
应用创建一个控制器实例去处理请求。
控制器创建一个动作实例并针对操作执行过滤器。
如果任何一个过滤器返回失败,则动作取消。
如果所有过滤器都通过,动作将被执行。
动作会加载一个数据模型,或许是来自数据库。
动作会渲染一个视图,把数据模型提供给它。
渲染结果返回给响应组件。
响应组件发送渲染结果给用户浏览器。
亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。