入口程序
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
啟動流程
- 基礎
bootstrap
行為,如必要的常量定義股毫、Composer加載器引入、配置讀取... - 生成被所有
worker/task
進程共享的程序全局期的對象 - 啟動時所有進程中只能執(zhí)行一次的操作圾笨,如前置Process的啟動...
-
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
配置的命名空間窃诉,另一部分是所有組件下的Command
、Bootstrap
赤套、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ù)...