Swoft 啟動流程

入口程序

Swoft入口是使用命令php bin/swoft start啟動HTTP服務器

$ php bin/swoft start
                          Server Information
 ********************************************************************
 * HTTP | host: 0.0.0.0, port: 80, type: 1, worker: 1, mode: 3
 * TCP  | host: 0.0.0.0, port: 8099, type: 1, worker: 1 (Enabled)
 ********************************************************************
 Server has been started. (master PID: 1, manager PID: 6)
 You can use CTRL + C to stop run.
docker@default: ~$ docker exec -it myswoft bash

root@f92d826e0248:/var/www/swoft# apt-get install procps
root@f92d826e0248:/var/www/swoft# ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.5 31.7 585500 321740 pts/0   Ssl+ 17:21   0:01 php-swoft master process (/var/www/swoft/bin/swoft)
root         6  0.0  0.8 511164  8352 pts/0    S+   17:21   0:00 php-swoft manager process
root         8  2.3  1.6 518440 16920 pts/0    S+   17:21   0:06 php-swoft task process
root         9  2.3  1.6 518900 17064 pts/0    S+   17:21   0:06 php-swoft worker process
root        10  3.3  1.8 520924 18948 pts/0    S+   17:21   0:09 php-swoft reload process
root        11  0.0  0.2  18136  2524 pts/1    Ss   17:23   0:00 bash
root        73  0.0  0.2  36640  2804 pts/1    R+   17:25   0:00 ps aux

啟動流程

啟動流程
  1. 基礎bootstrap行為,如必要的常量定義股毫、Composer加載器引入、配置讀取...
  2. 生成被所有worker/task進程共享的程序全局期的對象
  3. 啟動時所有進程中只能執(zhí)行一次的操作圾笨,如前置Process的啟動...
  4. Bean容器基本初始化以及項目啟動流程需要的coreBean的加載

啟動入口

啟動腳本

Swoft的啟動入口是bin/swoft腳本文件

$ vim bin/swoft

#!/usr/bin/env php
<?php
require_once __DIR__ . '/bootstrap.php';
$console = new \Swoft\Console\Console();
$console->run();

啟動文件

swoft腳本加載當前目錄下的bootstrap.php啟動文件

$ vim bin/bootstrap.php
<?php
// 加載Composer的autoload文件
require_once dirname(__DIR__) . '/vendor/autoload.php';
// 加載自定義的常量與別名配置
require_once dirname(__DIR__) . '/config/define.php';

// 初始化Bean對象工廠(種子工廠)
\Swoft\Bean\BeanFactory::init();

/* @var \Swoft\Bootstrap\Boots\Bootable $bootstrap*/
$bootstrap = \Swoft\App::getBean(\Swoft\Bootstrap\Bootstrap::class);
$bootstrap->bootstrap();

啟動文件的核心也就是框架的核心是BeanFactory對象工廠

什么是Bean呢乔遮?Swoft中的Bean是類的對象實例,詳情可參見《Swoft Bean》贫途。

啟動階段主要完成兩件事

  • 根據默認的注解掃描機制實例化Bean對象
  • 根據配置對Bean對象進行設置

這種通過配置來實例化類并設置對象屬性的方式在PHP框架中廣泛被應用

常量配置

查看自定義配置文件config/define.php文件

$ vim config/define.php
<?php
// 定義路徑分隔符常量
! defined('DS') && define('DS', DIRECTORY_SEPARATOR);
// 定義應用名稱常量
! defined('APP_NAME') && define('APP_NAME', 'swoft');
// 定義項目基礎路徑常量
! defined('BASE_PATH') && define('BASE_PATH', dirname(__DIR__, 1));

// 注冊別名
$aliases = [
    '@root'       => BASE_PATH,
    '@env'        => '@root',
    '@app'        => '@root/app',
    '@res'        => '@root/resources',
    '@runtime'    => '@root/runtime',
    '@configs'    => '@root/config',
    '@resources'  => '@root/resources',
    '@beans'      => '@configs/beans',
    '@properties' => '@configs/properties',
    '@console'    => '@beans/console.php',
    '@commands'   => '@app/command',
    '@vendor'     => '@root/vendor',
    '@public'     => '@root/public'
];
// 為應用設置別名
\Swoft\App::setAliases($aliases);

define.php自定義配置文件主要完成兩件事:定義PHP常量吧彪、為應用設置別名

別名機制

Swoft提供了別名機制,別名本質上是字符串的替換丢早,主要用于文件目錄或路徑姨裸。

可通過系統(tǒng)應用簡寫類App提供的方法對別名進行設置和獲取:

$ vi /vendor/swoft/framework/src/App.php
  • App::getAlias(string $alias): string 根據別名獲取實際值歌憨,返回字符串况脆。
  • App::setAlias(string $alias, string $path = null) 設置單個別名硝全,參數為字符串別名與路徑
  • App::setAliases(array $aliases) 同時設置多個別名,傳入用作配置的關聯數組赡艰。

簡單來說,Swoft別名機制是用來解決路徑問題的斤葱。

初始化對象

BeanFactory工廠用于對Bean種子的初始化慷垮,簡單來說就是統(tǒng)一地進行對象的實例化。

$ vim /vendor/swoft/framework/src/Bean/BeanFactory.php
class BeanFactory implements BeanFactoryInterface
{
    public static function init()
    {
        // 獲取property配置揍堕,讀取config/properties/目錄下的配置文件换帜,merge到同一個數組。
        $properties = self::getProperties();

        // 實例化依賴注入DI或控制反轉IoC的容器
        self::$container = new Container();
        // 設置依賴注入容器的屬性
        self::$container->setProperties($properties);
        // 自動加載服務器注解
        self::$container->autoloadServerAnnotation();
        
        // 獲取服務定義的Bean
        $definition = self::getServerDefinition();
        // IoC容器添加Bean的定義
        self::$container->addDefinitions($definition);
        // IoC容器初始化所有Bean
        self::$container->initBeans();
    }
}

種子工廠BeanFactory初始化主要是為了實例化Bean實例對象鹤啡,Bean又從哪里來呢惯驼?

Bean的來源有兩處:使用Annotation注解定義的Bean、服務定義的Bean

為了獲取使用注解定義的Bean递瑰,采取的方式是

  • 讀取屬性配置
  • 實例化依賴注入DI或控制反轉IoC的容器

為什么要這么做呢祟牲?通過配置來實例化類并設置對象屬性,這種做法在PHP框架中廣泛被應用抖部。

1. 讀取屬性配置

$properties = self::getProperties();
/**
 * @return array
 */
private static function getProperties()
{
    $properties = [];
    $config     = new Config();
    $dir        = App::getAlias('@properties');
    // 判斷屬性配置文件夾是否可讀
    if (is_readable($dir)) {
        $config->load($dir);
        $properties = $config->toArray();
    }

    return $properties;
}

屬性別名配置文件夾

'@properties' => '@configs/properties'

讀取配置的重點是加載配置文件说贝,使用全局配置管理器Config提供的load方法。

$ vim /vendor/swoft/framework/src/Core/Config.php

load方法簡單來說就是根據配置文件夾的文件路徑慎颗,循環(huán)所有讀取配置文件乡恕,合并為一個配置文件言询。

public function load(
    string $dir,
    array $excludeFiles = [],
    string $strategy = DirHelper::SCAN_BFS,
    string $structure = self::STRUCTURE_MERGE
): self {
    $mapping = [];
    if (StringHelper::contains($dir, ['@'])) {
        $dir = App::getAlias($dir);
    }
    if (!is_dir($dir)) {
        throw new \InvalidArgumentException('Invalid dir parameter');
    }

    $dir = DirHelper::formatPath($dir);
    $files = DirHelper::glob($dir, '*.php', $strategy);
    foreach ($files as $file) {
        if (! is_readable($file) || ArrayHelper::isIn($file, $excludeFiles)) {
            continue;
        }
        $loadedConfig = require $file;
        if (!\is_array($loadedConfig)) {
            throw new \InvalidArgumentException('Syntax error find in config file: ' . $file);
        }
        $fileName = DirHelper::basename([$file]);
        $key = current(explode('.', current($fileName)));
        switch ($structure) {
            case self::STRUCTURE_SEPARATE:
                $configMap = [$key => $loadedConfig];
                break;
            case self::STRUCTURE_MERGE:
            default:
                $configMap = $loadedConfig;
                break;
        }
        $mapping = ArrayHelper::merge($mapping, $configMap);
    }
    $this->properties = $mapping;
    return $this;
}

2. 設置依賴注入容器

啟動流程的核心在于容器,關于容器需理解什么是依賴注入(DI)和控制反轉(IoC)傲宜,詳情參見《IoC 控制反轉》运杭。

// 實例化依賴注入DI或控制反轉IoC的全局容器
self::$container = new Container();
// 設置依賴注入容器的屬性
self::$container->setProperties($properties);
// 自動加載服務器注解
self::$container->autoloadServerAnnotation();

這里的Container指的是全局容器,容器里面是裝的是什么東西呢函卒?是Bean辆憔,是實例化的對象。容器有什么用呢报嵌?管理對象的依賴關系虱咧。如何管理的呢?

$ vim /vendor/swoft/framework/src/Bean/Container.php

注解的生命周期

關于讀取配置并設置容器的配置锚国,上面已經分析過腕巡,這里重點分析容器自動加載注解autoloadServerAnnotation(),全局容器Container提供了autoloadServerAnnotation()方法用于注冊服務注解血筑,這里為什么會使用到注解绘沉,注解的作用是為了解耦,注解的詳情參見《Swoft Annotation 注解》云挟。

/**
 * 注冊服務器的注解
 */
public function autoloadServerAnnotation()
{
    // 從屬性配置文件中的bootScan選項中獲取待掃描的命名空間
    $bootScan = $this->getScanNamespaceFromProperties('bootScan');
    // 根據配置實例化服務注解資源
    $resource = new ServerAnnotationResource($this->properties);
    // 添加掃描的命名空間
    $resource->addScanNamespace($bootScan);
    // 獲取已解析的配置beans
    $definitions = $resource->getDefinitions();

    $this->definitions = array_merge($definitions, $this->definitions);
}

從屬性配置文件中的bootScan選項中獲取待掃描的命名空間

// 從屬性配置文件中的bootScan選項中獲取待掃描的命名空間
$bootScan = $this->getScanNamespaceFromProperties('bootScan');

從屬性配置文件夾config/properties/app.php會發(fā)現有一段bootScan應用啟動時掃描的配置

'bootScan'     => [
    'App\Commands',
    'App\Boot',
],

配置中存放的命名空間梆砸,默認是應用的命令行和啟動兩個命名空間,有什么用呢园欣?

// 根據配置實例化服務注解資源
$resource = new ServerAnnotationResource($this->properties);
// 添加需要掃描的命名空間
$resource->addScanNamespace($bootScan);
// 獲取已解析的配置beans
$definitions = $resource->getDefinitions();

這里重點分析getDefinitions()方法是如何獲取已經解析的配置Beans對象帖世。

/**
 * 獲取已解析的配置beans
 *
 * @return array
 * <pre>
 * [
 *     'beanName' => ObjectDefinition,
 *      ...
 * ]
 * </pre>
 */
public function getDefinitions()
{
    // 獲取掃描的PHP文件,即掃描上一步注冊進來的命名空間沸枯。
    $classNames     = $this->registerLoaderAndScanBean();
    // 獲取自定義配置的掃描文件即PHP類庫文件
    $fileClassNames = $this->scanFilePhpClass();
    // 將系統(tǒng)內置的類庫和用戶自定義的類庫合并以獲取所有需要掃描的類
    $classNames     = array_merge($classNames, $fileClassNames);
    // 循環(huán)遍歷所有類庫
    foreach ($classNames as $className) {
        // 解析每個類庫文件中的Bean注解
        $this->parseBeanAnnotations($className);
    }
    // 解析注解數據并存放到definitions成員屬性中
    $this->parseAnnotationsData();
    // 返回所有的注解數據
    return $this->definitions;
}

這里總結下日矫,Swoft使用的組件化方式,將不同的組件使用IoC容器進行統(tǒng)一管理實例化對象(Bean)的依賴關系绑榴。然后通過注解的進行使用哪轿。在容器中首先會根據命名空間掃描注解,Swoft中的注解主要包括兩部分翔怎,一部分是屬性配置文件夾下config/properties/app.php應用屬性配置中bootScan配置的命名空間窃诉,另一部分是所有組件下的CommandBootstrap赤套、Aop命名空間飘痛。

3. 初始化對象

// 獲取服務注解數據
$definition = self::getServerDefinition();
// IoC容器添加注解數據
self::$container->addDefinitions($definition);
// IoC容器初始化Bean
self::$container->initBeans();

這里需要注意下關于definition是什么,根據注釋上看是已解析Bean的規(guī)則容握。

首先來看下是如何獲取服務的注解數據的

/**
 * @return array
 * @throws \InvalidArgumentException
 */
private static function getServerDefinition(): array
{
    // 通過常量配置文件config/define.php中的控制臺別名@console獲取對應文件保存路徑
    $file             = App::getAlias('@console');

    // 判斷配置中的文件是否可讀進而引入
    $configDefinition = [];
    if (\is_readable($file)) {
        $configDefinition = require_once $file;
    }

    // 獲取框架核心的Bean
    $coreBeans  = self::getCoreBean(BootBeanCollector::TYPE_SERVER);

    // 將框架核心Bean和@console中定義的注解合并后返回
    return ArrayHelper::merge($coreBeans, $configDefinition);
}

這里的@console是干什么用的呢宣脉?字面意思是控制臺,與其相關的應該是一些自定義命令的功能剔氏。

/**
 * 定義配置bean
 *
 * @param array $definitions
 */
public function addDefinitions(array $definitions)
{
    $resource          = new DefinitionResource($definitions);
    $this->definitions = array_merge($resource->getDefinitions(), $this->definitions);
}
/**
 * @throws \InvalidArgumentException
 * @throws \ReflectionException
 */
public function initBeans()
{
    $autoInitBeans = $this->properties['autoInitBean'] ?? false;
    if (!$autoInitBeans) {
        return;
    }

    // 循環(huán)初始化
    foreach ($this->definitions as $beanName => $definition) {
        $this->get($beanName);
    }
}

autoInitBean配置位于config/properties/app.php屬性配置文件中塑猖,用于配置是否初始化Bean竹祷,為什么要有這個配置選項呢?

/**
 * 獲取一個bean
 *
 * @param string $name 名稱
 *
 * @return mixed
 * @throws \ReflectionException
 * @throws \InvalidArgumentException
 */
public function get(string $name)
{
    // 已經創(chuàng)建
    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);
}

Swoft是如何根據Bean的名稱來對Bean進行初始化的呢羊苟?

/**
 * 創(chuàng)建bean
 *
 * @param string           $name             名稱
 * @param ObjectDefinition $objectDefinition bean定義
 *
 * @return object
 * @throws \ReflectionException
 * @throws \InvalidArgumentException
 */
private function set(string $name, ObjectDefinition $objectDefinition)
{
    // bean創(chuàng)建信息
    $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);

    // 執(zhí)行初始化方法
    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初始化

  • 注解解析后獲取類相關信息
  • 注入構造函數
  • 初始化類并執(zhí)行構造函數
  • 注入屬性
  • 執(zhí)行初始化方法
  • AOP處理找到實際代理的類
  • 單例處理
  • 返回生成的Bean對象

總結下塑陵,Swoft的核心是使用IoC去掃描注解并通過注解注解初始化Bean,需要關注的是啟動時是如何掃描注解文件践险,另外掃描到的注解是如何進行初始化成Bean的猿妈。

通過BeanFactory::init()初始化后就可以直接使用Bean

BeanFactory::getBean($bean_name)

App::getBean($bean_name)

啟動

現在回到bin/bootstrap.php文件中吹菱,繼續(xù)分析最后的環(huán)節(jié)巍虫,分析如何啟動應用。

/* @var \Swoft\Bootstrap\Boots\Bootable $bootstrap*/
$bootstrap = \Swoft\App::getBean(\Swoft\Bootstrap\Bootstrap::class);
$bootstrap->bootstrap();

通過IoC容器根據Bootstrap類生成啟動對象

$bootstrap = \Swoft\App::getBean(\Swoft\Bootstrap\Bootstrap::class);

啟動對象執(zhí)行啟動方法

$bootstrap->bootstrap();

獲取啟動項鳍刷,根據啟動項中設置的排序值進行排序后占遥,循環(huán)遍歷依次通過每個啟動獲取啟動的Bean對象,并執(zhí)行每個對象中的啟動方法输瓜。

public function bootstrap()
{
    $bootstraps = BootstrapCollector::getCollector();
    $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();
    }
}

通過框架源碼中可以觀察到需要項目啟動所需的類庫瓦胎,可以使用var_dump($bootstraps)打印查看。

啟動文件

未完待續(xù)...

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末尤揣,一起剝皮案震驚了整個濱河市搔啊,隨后出現的幾起案子,更是在濱河造成了極大的恐慌北戏,老刑警劉巖负芋,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異嗜愈,居然都是意外死亡旧蛾,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進店門蠕嫁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來锨天,“玉大人,你說我怎么就攤上這事剃毒〔“溃” “怎么了?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵赘阀,是天一觀的道長益缠。 經常有香客問我,道長纤壁,這世上最難降的妖魔是什么左刽? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮酌媒,結果婚禮上欠痴,老公的妹妹穿的比我還像新娘迄靠。我一直安慰自己,他們只是感情好喇辽,可當我...
    茶點故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布掌挚。 她就那樣靜靜地躺著,像睡著了一般菩咨。 火紅的嫁衣襯著肌膚如雪吠式。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天抽米,我揣著相機與錄音特占,去河邊找鬼。 笑死云茸,一個胖子當著我的面吹牛是目,可吹牛的內容都是我干的。 我是一名探鬼主播标捺,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼懊纳,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了亡容?” 一聲冷哼從身側響起嗤疯,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎闺兢,沒想到半個月后茂缚,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡列敲,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年阱佛,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片戴而。...
    茶點故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡凑术,死狀恐怖,靈堂內的尸體忽然破棺而出所意,到底是詐尸還是另有隱情淮逊,我是刑警寧澤,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布扶踊,位于F島的核電站泄鹏,受9級特大地震影響,放射性物質發(fā)生泄漏秧耗。R本人自食惡果不足惜备籽,卻給世界環(huán)境...
    茶點故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧车猬,春花似錦霉猛、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至伏嗜,卻和暖如春坛悉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背承绸。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工裸影, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人八酒。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓空民,卻偏偏與公主長得像刃唐,于是被迫代替她去往敵國和親羞迷。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,446評論 2 348

推薦閱讀更多精彩內容