瞭解composer的autoload psr0 psr4 加載機制php
瞭解spl_autoload_registerweb
瞭解依賴注入的實現原理反射bootstrap
瞭解經常使用魔術方法__set,__get,__callsession
熱情與專一app
加載composer 的自動加載器,支持了PSR-0 PSR-4composer
require(__DIR__ . '/../vendor/autoload.php');
進行常量的定義,而且聲明瞭最基本的方法例如getVersion框架
require __DIR__ . '/BaseYii.php';
加載Yii本身的autoload加載器,從classmap中尋找,指定的類,若是沒有找到,會解析名稱到路徑。yii
spl_autoload_register(['Yii', 'autoload'], true, true); Yii::$classMap = require __DIR__ . '/classes.php';
todo 容器生成webapp
Yii::$container = new yii\di\Container();
開始生成一個應用主體,而且加載了config配置,直接運行優化
(new yii\web\Application($config))->run();
application is the base class for all web application classes.
Yii::$app
是應用主體的實例,一個請求只會生成一個應用主體
在Application
類中,定義了初始的defaultRoute
,以及coreComponent
核心組件的列表,還有一些請求和相應相關的方法
web/application
=>base/application
=>base/Model
=>id/ServiceLocator
=>base/component
=>base/object
=>base/configurable
public function __construct($config = []) { Yii::$app = $this; // 這樣在任何地方均可以經過靜態方法的方式,來調用應用主體 static::setInstance($this); // 將請求過來的的類實力,進行保存 $this->state = self::STATE_BEGIN; $this->preInit($config); // 進行config的初始化,給路徑起別名,設置時區等,而且最後加載了核心組件 $this->registerErrorHandler($config); // 註冊錯誤句柄,用來捕捉和處理錯誤的方法 Component::__construct($config); // }
其中preInit會進行一個註冊核心組件,這裏web的入口進行了擴展,包含了request等
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'], ]); }
講configure格式爲對象,存儲到應用主體中
public function __construct($config = []) { if (!empty($config)) { Yii::configure($this, $config); } $this->init(); }
這裏咱們來看下組件是如何註冊到應用主體中的,這個->
實際上調用的是__Set
魔術方法,
那咱們再看這個$this
是什麼,很明顯是指yii\web\application
public static function configure($object, $properties) { foreach ($properties as $name => $value) { $object->$name = $value; } return $object; }
咱們從webapplication向parent一層一層的找,找到了__set 的定義,在basecomponent
public function __set($name, $value) { $setter = 'set' . $name; if (method_exists($this, $setter)) { // set property $this->$setter($value); return; } elseif (strncmp($name, 'on ', 3) === 0) { // on event: attach event handler $this->on(trim(substr($name, 3)), $value); return; } elseif (strncmp($name, 'as ', 3) === 0) { // as behavior: attach behavior $name = trim(substr($name, 3)); $this->attachBehavior($name, $value instanceof Behavior ? $value : Yii::createObject($value)); return; } // behavior property $this->ensureBehaviors(); foreach ($this->_behaviors as $behavior) { if ($behavior->canSetProperty($name)) { $behavior->$name = $value; return; } } if (method_exists($this, 'get' . $name)) { throw new InvalidCallException('Setting read-only property: ' . get_class($this) . '::' . $name); } throw new UnknownPropertyException('Setting unknown property: ' . get_class($this) . '::' . $name); }
上面的set方法,會遍歷config的屬性,來交給不一樣的set方法處理,果真剛好存在了一個setComponents
,用來處理組件
public function setComponents($components) { foreach ($components as $id => $component) { $this->set($id, $component); } }
最終組件配置就這樣被註冊到了$this->_definitions
裏面,後面咱們能夠經過$this->get($id)
來獲取組件配置
public function set($id, $definition) { unset($this->_components[$id]); if ($definition === null) { unset($this->_definitions[$id]); return; } if (is_object($definition) || is_callable($definition, true)) { // an object, a class name, or a PHP callable $this->_definitions[$id] = $definition; } elseif (is_array($definition)) { // a configuration array if (isset($definition['class'])) { $this->_definitions[$id] = $definition; } else { throw new InvalidConfigException("The configuration for the \"$id\" component must contain a \"class\" element."); } } else { throw new InvalidConfigException("Unexpected configuration type for the \"$id\" component: " . gettype($definition)); } }
注意這個init,並非當前類下面的init方法,而是被webapplication 覆蓋
public function init() { $this->state = self::STATE_INIT; $this->bootstrap(); }
一樣bootstrap方法也被覆蓋
protected function bootstrap() { $request = $this->getRequest(); Yii::setAlias('@webroot', dirname($request->getScriptFile())); Yii::setAlias('@web', $request->getBaseUrl()); parent::bootstrap(); }
看下getRequest是怎麼回事,跟蹤代碼,它經過get方法,來從_definitions中獲取request
對應的配置
而後根據配置中的yiiwebrequest 來進行建立對象,其中$type 就是配置
public static function createObject($type, array $params = []) { if (is_string($type)) { return static::$container->get($type, $params); } elseif (is_array($type) && isset($type['class'])) { $class = $type['class']; unset($type['class']); return static::$container->get($class, $params, $type); // request 走的是這裏 } elseif (is_callable($type, true)) { return static::$container->invoke($type, $params); } elseif (is_array($type)) { throw new InvalidConfigException('Object configuration must be an array containing a "class" element.'); } throw new InvalidConfigException('Unsupported configuration type: ' . gettype($type)); }
進行完web層的引導以後,繼續進行base層的引導,包括對配置中bootstrap的引導註冊,會直接經過容器獲得實例。(具體不詳)
在上面的new的過程當中,完成了組件的註冊,配置的初始化,而且學到了get是createObject經過依賴注入的方法,獲取組件對象
前面全部的配置和準備都初始化完畢以後,要進行請求處理了。
這一句的run方法,就是處理請求的整個啓動入口
$application->run();
使用handleRequest方法來處理請求,而且參數是request實例,其中handleRequest方法要看webapplication層的封裝
$response = $this->handleRequest($this->getRequest());
list($route, $params) = $request->resolve();
咱們再看Urlmanager的時候,發現裏面定義的routeParam是r也就是默認接受參數的值(重要)
實際上下面這段代碼完成的就是解析了$_GET的值,從裏面尋找routeParam 定義的值(r)所表明的內容
$result = Yii::$app->getUrlManager()->parseRequest($this);
這裏要調用action方法了
$this->requestedRoute = $route; $result = $this->runAction($route, $params);
實際上runAction中的主要內容就是createController
的實現:
// parts 分爲兩部分,0 是controller的實例,1 是實例裏面的方法名稱 $parts = $this->createController($route); ... ... /* @var $controller Controller */ // 這是一個跟蹤優化,否則controller->runaction 就定位不了了 list($controller, $actionID) = $parts; $oldController = Yii::$app->controller; Yii::$app->controller = $controller; $result = $controller->runAction($actionID, $params);
若是遇到了控制器,那麼直接返回控制器對象和方法route的名稱,這裏route相似於action方法名稱,代碼略微繁瑣,可是很清晰,具體實現就是
例如r=site
尋找controller,找到site控制器,直接實例化
例如r=site/index
構造控制器,而且id爲site、route爲index
例如r=site/index/test
發現沒有找到控制器,那麼從模塊中獲取,這裏是從新構造id爲site/index route爲test,而後調用createControllerById
方法來獲取控制器(具體不詳,不過應該是經過namespace定位了)
並且createController
直接返回了controller的實例
而後咱們在createAction
中找到解析action方法名稱的代碼
例如r=site/index-test 那麼下面對應的methodName就是actionIndexTest
$methodName = 'action' . str_replace(' ', '', ucwords(implode(' ', explode('-', $id))));
將調用action方法的值,進行返回,而後直接交給yii\web\Response
做爲data屬性的一部分。最後調用send方法,進行輸出。
public function send() { if ($this->isSent) { return; } $this->trigger(self::EVENT_BEFORE_SEND); $this->prepare(); $this->trigger(self::EVENT_AFTER_PREPARE); $this->sendHeaders(); $this->sendContent(); $this->trigger(self::EVENT_AFTER_SEND); $this->isSent = true; }
經過attacheBehavior
註冊行爲以後,其實是添加到了$this
的_behaviors
屬性中
那麼行爲中的屬性,就添加到了,_behaviors
中
進行直接調用行爲裏面的方法的時候,實際上觸發了yii\base\Component
裏面的__call
魔術方法