date: 2018-8-01 14:22:17
title: swoft| 源碼解讀系列二: 啓動階段, swoft 都幹了些啥?
description: 閱讀 sowft 框架源碼, 瞭解 sowft 啓動階段的那些事兒php
小夥伴剛接觸 swoft 的時候會感受 壓力有點大, 更直觀的說法是 難. 開發組是不同意 難 這個說法的, swoft 的代碼都是 php 實現的, 而 php 又是 世界上最好的語言, swoft 的代碼閱讀起來是很輕鬆的.html
以後開發組會用 系列源碼 解讀文章, 深刻解析 swoft. 咱們相信, 這會成爲一段輕鬆之旅.laravel
swoft 源碼解讀系列一: 好難! swoft demo 都跑不起來怎麼破? docker 瞭解一下唄~
swoft 源碼解讀系列二: 啓動階段, swoft 都幹了些啥?web
附上社區小夥伴 隨風 製做的流程圖:docker
看過 官方文檔-服務啓動與管理 章節, 就知道 swoft 的入口時 php bin/swoft start
, 用來啓動 http server. 運行這個命令, 就爲咱們打開了新世界的大門bootstrap
root@e38a7e5aff40 /v/w/s/swoft# ps aux PID USER TIME COMMAND 1 root 0:00 php -a 708 root 0:01 php-swoft master process (bin/swoft) 709 root 0:00 php-swoft manager process 711 root 0:01 php-swoft task process 712 root 0:01 php-swoft worker process 713 root 0:49 php-swoft reload process 779 root 0:00 ps aux
熟悉 swoole-wiki 的小夥伴, 就能看到熟悉的:segmentfault
swoole-wiki 上的 運行流程圖 和 進程/線程結構圖 值得細細品味, 這是咱們以後理解和使用 swoole 進行服務器開發的基礎, 這裏按下暫時不表.數組
而咱們爲了弄懂 swoft啓動階段都幹了些啥, 能夠直接運行 php bin/swoft
, 慢慢 調試/輸出 便可. 是的, 沒有什麼高級技巧, var_dump() + die()
便可bash
沒錯, 這個工具就是 phpstorm, 沒使用 phpstorm 的小夥伴趕忙用起來, 下面以 window 下的快捷鍵爲例:服務器
還有不少好用的功能, 請查看菜單欄的 navigate
菜單欄, 去發現驚喜吧~
PS: 註釋! 註釋! 註釋!
bin/swoft
文件很簡單:
#!/usr/bin/env php <?php require_once __DIR__ . '/bootstrap.php'; $console = new \Swoft\Console\Console(); $console->run();
咱們先來看 bin/bootstrap.php
:
require_once dirname(__DIR__) . '/vendor/autoload.php'; require_once dirname(__DIR__) . '/config/define.php'; // init the factory of bean \Swoft\Bean\BeanFactory::init(); /* @var \Swoft\Bootstrap\Boots\Bootable $bootstrap*/ $bootstrap = \Swoft\App::getBean(\Swoft\Bootstrap\Bootstrap::class); $bootstrap->bootstrap();
第一步加載 composer 的 autoload 文件, 使用 composer 的同窗應該都知道吧, 不過你知道 autoload 的原理麼?
第二步是 config/define.php
文件, 咱們進去看看:
// Project base path ! defined('BASE_PATH') && define('BASE_PATH', dirname(__DIR__, 1)); // Register alias $aliases = [ '@root' => BASE_PATH, ]; \Swoft\App::setAliases($aliases);
作了 2 件事:
swoft 的第一個特性 -- 別名機制 來了. 挺新鮮的詞兒, 本質很簡單 -- 字符串替換 而已, 好比上面咱們設置 @root
, 咱們直接打印看看:
$tmp = \Swoft\App::getAlias('@root'); var_dump($tmp);die; root@e38a7e5aff40 /v/w/s/swoft# php bin/swoft string(21) "/var/www/swoole/swoft"
使用看看:
$tmp1 = \Swoft\App::getAlias('@root'); $tmp2 = \Swoft\App::getAlias('@root/foo/bar'); var_dump($tmp1, $tmp2);die; root@e38a7e5aff40 /v/w/s/swoft# php bin/swoft string(21) "/var/www/swoole/swoft" string(29) "/var/www/swoole/swoft/foo/bar"
目前 swoft 中的別名機制在用在 文件目錄/路徑 上, 熟悉 yii框架 的小夥伴知道, yii中別名機制用的場景更多一些, 還能拼接 url 等地方. 不過無論使用多少場景, 本質都是 字符串替換.
那爲何不直接使用 PHP常量 這種常規方式, 而要使用別名機制呢? 別名機制不是更優雅麼
到了框架的核心部分了, 閱讀這塊的代碼要有耐心一點:
// init the factory of bean \Swoft\Bean\BeanFactory::init();
進入 init()
, 先看第一個:
$properties = self::getProperties(); // 獲取 property 配置 var_dump($properties);die;
看源碼和調試驗證輔助: 讀取 config/properties
下的配置(文件), merge
到同一個數組裏了
再看第二步, 核心的核心, 容器Container 來了, 這裏再也不贅述 依賴注入DI/控制反轉IoC 等基礎知識, 不熟悉的小夥伴要去補補哦~
self::$container = new Container(); self::$container->setProperties($properties); self::$container->autoloadServerAnnotation(); /** * Register the annotation of server */ public function autoloadServerAnnotation() { $bootScan = $this->getScanNamespaceFromProperties('bootScan'); // 獲取 property 配置中的 bootScan 配置項 var_dump($bootScan); $resource = new ServerAnnotationResource($this->properties); $resource->addScanNamespace($bootScan); // 關鍵在這一句, 要掃描哪些命名空間(文件) $definitions = $resource->getDefinitions(); var_dump($definitions);die; $this->definitions = array_merge($definitions, $this->definitions); }
重點來看看 $resource->addScanNamespace($bootScan)
$resource->addScanNamespace($bootScan)
繼承了抽象基類繞了一下, 最後其實走到了這裏
<?php namespace Swoft\Bean\Resource; use Swoft\App; use Swoft\Helper\ComponentHelper; /** * The annotation resource of server */ class ServerAnnotationResource extends AnnotationResource { /** * Register the scaned namespace */ public function registerNamespace() // 繼承了抽象基類繞了一下, 最後其實走到了這裏 { $swoftDir = dirname(__FILE__, 5); // 默認掃描路徑, swoft 框架各個組件目錄 var_dump($swoftDir); var_dump(App::getAlias('@vendor/swoft')); // 使用 alias 能夠得出同樣的結果, 能夠思考一下爲何這裏不用別名機制呢? $componentDirs = scandir($swoftDir); foreach ($componentDirs as $component) { if ($component == '.' || $component == '..') { continue; } $componentDir = $swoftDir . DS . $component; $componentCommandDir = $componentDir . DS . 'src'; if (! is_dir($componentCommandDir)) { continue; } $ns = ComponentHelper::getComponentNamespace($component, $componentDir); $this->componentNamespaces[] = $ns; // console component if ($component == $this->consoleName) { // console 組件特殊處理 $this->scanNamespaces[$ns] = $componentCommandDir; continue; } foreach ($this->serverScan as $dir) { // 預約義的命名空間 $scanDir = $componentCommandDir . DS . $dir; if (!is_dir($scanDir)) { continue; } $scanNs = $ns . "\\" . $dir; $this->scanNamespaces[$scanNs] = $scanDir; } } } } /** * @var array */ protected $serverScan = [ 'Command', 'Bootstrap', 'Aop', ];
// $this->scanNamespaces 的內容示例 ["Swoft\WebSocket\Server\Bootstrap"]=> string(65) "/var/www/swoole/swoft/vendor/swoft/websocket-server/src/Bootstrap"
恭喜你, 到這裏你已經理解了一半的註解功能:
config/properties
下 bootScan
配置的命名空間Command Bootstrap Aop
命名空間, 其中 console
組件特殊處理若是到這裏你感受比較難理解, 你須要補充一下基礎知識:
另外, 上面加的測試代碼 var_dump(App::getAlias('@vendor/swoft'));
, 能夠思考一下 swoft 的別名機制就是爲了解決 路徑問題, 爲何這裏又不用呢?
$definitions = $resource->getDefinitions();
對應的內容:
/** * 獲取已解析的配置beans * * @return array * <pre> * [ * 'beanName' => ObjectDefinition, * ... * ] * </pre> */ public function getDefinitions() { // 獲取掃描的PHP文件 $classNames = $this->registerLoaderAndScanBean(); // 掃描上一步註冊進來的命名空間 $fileClassNames = $this->scanFilePhpClass(); // 額外配置的掃描文件, 你們能夠嘗試一下在哪配置的哦 $classNames = array_merge($classNames, $fileClassNames); // 獲取到全部須要掃面的類 foreach ($classNames as $className) { $this->parseBeanAnnotations($className); // 解析bean註解 } $this->parseAnnotationsData(); // 解析註解數據, 存放到 $this->definitions 中 return $this->definitions; // 最後, 咱們使用這個就能夠獲取到註解解析出來的了類啦 }
// 看一看註解解析出來的例子 ["Swoft\WebSocket\Server\Bootstrap\CoreBean"]=> object(Swoft\Bean\ObjectDefinition)#126 (7) { ["name":"Swoft\Bean\ObjectDefinition":private]=> string(41) "Swoft\WebSocket\Server\Bootstrap\CoreBean" ["className":"Swoft\Bean\ObjectDefinition":private]=> string(41) "Swoft\WebSocket\Server\Bootstrap\CoreBean" ["scope":"Swoft\Bean\ObjectDefinition":private]=> int(1) ["ref":"Swoft\Bean\ObjectDefinition":private]=> string(0) "" ["constructorInjection":"Swoft\Bean\ObjectDefinition":private]=> NULL ["propertyInjections":"Swoft\Bean\ObjectDefinition":private]=> array(0) { } ["methodInjections":"Swoft\Bean\ObjectDefinition":private]=> array(0) { } }
這裏隱藏了掃描不一樣類型註解的細節, 由於咱們後面閱讀不一樣組件源碼時會一一遇到, 這裏只要理解大體原理便可
後面的 2 句比較簡單:
$definition = self::getServerDefinition(); self::$container->addDefinitions($definition); /** * @return array * @throws \InvalidArgumentException */ private static function getServerDefinition(): array { $file = App::getAlias('@console'); $configDefinition = []; if (\is_readable($file)) { $configDefinition = require_once $file; } $coreBeans = self::getCoreBean(BootBeanCollector::TYPE_SERVER); var_dump($coreBeans);die; return ArrayHelper::merge($coreBeans, $configDefinition); }
簡單打印一下就能夠知道結果:
root@e38a7e5aff40 /v/w/s/swoft# php bin/swoft array(1) { ["commandRoute"]=> array(1) { ["class"]=> string(35) "Swoft\Console\Router\HandlerMapping" } }
self::$container->initBeans(); // 進去查看 /** * @throws \InvalidArgumentException * @throws \ReflectionException */ public function initBeans() { $autoInitBeans = $this->properties['autoInitBean'] ?? false; if (!$autoInitBeans) { return; } // 循環初始化 foreach ($this->definitions as $beanName => $definition) { $this->get($beanName); } } /** * 獲取一個bean * * @param string $name 名稱 * * @return mixed * @throws \ReflectionException * @throws \InvalidArgumentException */ public function get(string $name) { // 已經建立 if (isset($this->singletonEntries[$name])) { // 單例, 初始化過就直接返回 return $this->singletonEntries[$name]; } // 未定義 if (!isset($this->definitions[$name])) { throw new \InvalidArgumentException(sprintf('Bean %s not exist', $name)); } /* @var ObjectDefinition $objectDefinition */ $objectDefinition = $this->definitions[$name]; return $this->set($name, $objectDefinition); // 沒有初始化則進行初始化 } /** * 建立bean * * @param string $name 名稱 * @param ObjectDefinition $objectDefinition bean定義 * * @return object * @throws \ReflectionException * @throws \InvalidArgumentException */ private function set(string $name, ObjectDefinition $objectDefinition) { // bean建立信息 $scope = $objectDefinition->getScope(); $className = $objectDefinition->getClassName(); $propertyInjects = $objectDefinition->getPropertyInjections(); $constructorInject = $objectDefinition->getConstructorInjection(); if ($refBeanName = $objectDefinition->getRef()) { return $this->get($refBeanName); } // 構造函數 $constructorParameters = []; if ($constructorInject !== null) { $constructorParameters = $this->injectConstructor($constructorInject); } $reflectionClass = new \ReflectionClass($className); $properties = $reflectionClass->getProperties(); // new實例 $isExeMethod = $reflectionClass->hasMethod($this->initMethod); $object = $this->newBeanInstance($reflectionClass, $constructorParameters); // 屬性注入 $this->injectProperties($object, $properties, $propertyInjects); // 執行初始化方法 if ($isExeMethod) { $object->{$this->initMethod}(); } if (!$object instanceof AopInterface) { $object = $this->proxyBean($name, $className, $object); } // 單例處理 if ($scope === Scope::SINGLETON) { $this->singletonEntries[$name] = $object; } return $object; }
Bean 初始化的全部細節都在這裏了:
init()
也會執行的到這裏 整個 swoft 核心中的核心 就已經呈如今你面前了, 總結起來也很簡單:
有了 \Swoft\Bean\BeanFactory::init();
之後, 咱們須要使用 Bean, 只須要:
\Swoft\Bean\BeanFactory::getBean('xxx'); // 下面的寫法只是一層封裝而已 \Swoft\App::getBean('xxx'); /** * get bean * * @param string $name 名稱 * * @return mixed */ public static function getBean(string $name) { return ApplicationContext::getBean($name); }
經過在合適的地方打印:
/* @var \Swoft\Bootstrap\Boots\Bootable $bootstrap*/ $bootstrap = \Swoft\App::getBean(\Swoft\Bootstrap\Bootstrap::class); var_dump($bootstrap); $bootstrap->bootstrap(); /** * bootstrap */ public function bootstrap() { $bootstraps = BootstrapCollector::getCollector(); // 須要執行哪些 bootstrap var_dump($bootstraps);die; $temp = \array_column($bootstraps, 'order'); \array_multisort($temp, SORT_ASC, $bootstraps); foreach ($bootstraps as $bootstrapBeanName => $name){ /* @var Bootable $bootstrap*/ $bootstrap = App::getBean($bootstrapBeanName); $bootstrap->bootstrap(); } }
結果以下:
root@e38a7e5aff40 /v/w/s/swoft# php bin/swoft object(Bootstrap_5b6dd8716a6dc)#209 (1) { ["__handler_5b6dd8716a6dc":"Bootstrap_5b6dd8716a6dc":private]=> object(Swoft\Proxy\Handler\AopHandler)#188 (1) { # 用到了 aop ["target":"Swoft\Proxy\Handler\AopHandler":private]=> object(Swoft\Bootstrap\Bootstrap)#186 (0) { } } } array(3) { # 真正執行的 bootstrap ["Swoft\Bootstrap\Boots\InitPhpEnv"]=> # init php env array(2) { ["name"]=> string(0) "" ["order"]=> int(2) } ["Swoft\Bootstrap\Boots\LoadEnv"]=> # 加載 .env 文件 array(2) { ["name"]=> string(0) "" ["order"]=> int(1) } ["Swoft\Bootstrap\Boots\LoadInitConfiguration"]=> # 加載 config 目錄的其餘配置 array(2) { ["name"]=> string(0) "" ["order"]=> int(3) } }
至此, bootstrap 階段的全部工做就完成了
回答 bean 是啥以前, 先記住: 一切皆對象
咱們使用對面對象的方式來對問題進行抽象, 並使用抽象出來的類實例化後的對象來解決問題, 而實例化後的對象, 就是 swoft 中一個又一個的 Bean
回顧咱們整個 bootstrap 階段, 能夠歸納爲自動化作了 2 件事情:
config/
.env
等配置中中的 bean/property, 對 swoft 中的 Bean 進行配置(實例化 Bean, 或者配置 Bean 的 property)這樣經過配置來示例化類和配置對象屬性的方式, 在 php 框架中大型其道, 典型的如 yii/laravel.