Swoft2 框架精华教程:Swoft 的启动过程与核心源代码解析
- 作者: 刘杰
- 来源: 技术那些事
- 阅读:270
- 发布: 2025-06-23 11:27
- 最后更新: 2025-08-23 06:58
框架初始化阶段
日志模块加载
路径相关初始化
-
初始化
basePath路径,如果未提供,会根据当前执行文件地址进行寻找项目主目录。 -
初始化系统别名
别名 路径 @base swoft项目目录@app swoft/app @config swoft/config @runtime swoft/runtime @resource swoft/resource
主要进程初始化
获取框架启动相关的各个主要进程,:
实例化应用进程实例对象,应用进程对象包含以下主要进程实例对象(每个进程均持有应用的对象实例)。
- 环境信息进程实例对象
- 配置信息进程实例对象
- 注解信息进程实例对象
- Bean 进程实例对象
- 控制台进程实例对象
php
protected function processors(): array
{
return [
new EnvProcessor($this),
new ConfigProcessor($this),
new AnnotationProcessor($this),
new BeanProcessor($this),
new EventProcessor($this),
new ConsoleProcessor($this),
];
}
运行阶段
运行阶段,指的是应用进程对象处理的过程。这个过程又分为以下六个更详细的阶段。
这六个阶段,是对初始化过程中的各个进程实例对象的执行过程。
环境信息进程实例处理阶段
获取 @base/.env 文件
php
// Load env info
$factory = new DotenvFactory([
new EnvConstAdapter,
new PutenvAdapter,
new ServerConstAdapter
]);
类图
classDiagram
direction LR
class AdapterInterface {
<<interface>>
+ isSupport(): bool;
+ get(string $name): PhpOption;
+ set(string $name, mixed? $val = null): void;
+ clear(string $name): void;
}
class EnvConstAdapter
class PutenvAdapter
class ServerConstAdapter
EnvConstAdapter ..|> AdapterInterface
PutenvAdapter ..|> AdapterInterface
ServerConstAdapter ..|> AdapterInterface
- EnvConstAdapter(
$_ENV环境变量) - PutenvAdapter(系统环境变量中获取信息)
- ServerConstAdapter(
$_SERVER变量中获取信息)
从以上不同的环境变量来源中,获取所有的环境变量信息。保存在 Dotenv 对象内部 loader 加载器中。
配置信息进程实例处理阶段
设置两个常量,如果环境变量中有这两变量的配置,则使用他们,没有配置,默认都是0。
php
define('APP_DEBUG', (int)env('APP_DEBUG', 0));
define('SWOFT_DEBUG', (int)env('SWOFT_DEBUG', 0));
注解信息进程实例处理阶段
实例化相关对象
获取注解要加载的相关信息,根路径,通知处理函数,禁用的自动加载器,不包含的 Psr4Prefixes 目录(在每个组件的 Autoload 自动加载类中,有获取当前组件的 Psr4Prefixes 函数)。
php
// Find AutoLoader classes. Parse and collect annotations.
AnnotationRegister::load([
'inPhar' => IN_PHAR,
'basePath' => $app->getBasePath(),
'notifyHandler' => [$this, 'notifyHandle'],
// TODO force load framework components: bean, error, event, aop
'disabledAutoLoaders' => $app->getDisabledAutoLoaders(),
'excludedPsr4Prefixes' => $app->getDisabledPsr4Prefixes(),
]);
// 获取自动加载所有组件类后,对注解信息的一些统计信息,并且打印到控制台
$stats = AnnotationRegister::getClassStats();
CLog::info(
'Annotations is scanned(autoloader %d, annotation %d, parser %d)',
$stats['autoloader'],
$stats['annotation'],
$stats['parser']
);
AnnotationRegister 注解注册器中又实例化了一个注解资源对象(AnnotationResource),加载器默认注册类加载,通过 class_exists 检测来加载类。
检测自动类加载器,是否默认为 composer 的类加载器。如果是,将 AnnotationResource 对象的 classLoader 属性绑定 composer 的类加载器实例。
获取包含文件,将包含文件放到 AnnotationResource 对象的 includedFiles 属性中。
自动加载所有组件相关的类
通过 AnnotationResource 中的 load 方法,首先通过 composer 类加载器,可以获取到当前主项目(swoft应用)的路径前缀(composer.json 中,autoload/autolaod-dev 对应的目录),通过对这些目录进行遍历,可以获取主项目中每个命名空间和目录绑定的关系。
php
public function load(): void
{
// 获取主项目中相关的 psr4 标准对应的信息
$prefixDirsPsr4 = $this->classLoader->getPrefixesPsr4();
// 遍历主项目中的 psr4 相关信息
foreach ($prefixDirsPsr4 as $ns => $paths) {
// Only scan namespaces
// 是否配置了特定的扫描命名空间,如果是只扫描这个命名空间,其他空间直接跳过
if ($this->onlyNamespaces && !in_array($ns, $this->onlyNamespaces, true)) {
$this->notify('excludeNs', $ns);
continue;
}
// It is excluded psr4 prefix
// 判断当前 psr4 是否配置了排除规则,如果配置过,也跳过不扫
if ($this->isExcludedPsr4Prefix($ns)) {
AnnotationRegister::addExcludeNamespace($ns);
$this->notify('excludeNs', $ns);
continue;
}
// Find package/component loader class
// 在需要扫描的命名空间对应的文件目录中,扫描是否有自动加载类 AutoLoader.php
foreach ($paths as $path) {
// 获取 AutoLoader.php 文件的路径(这里注意不要写成 Autoload.php)
$loaderFile = $this->getAnnotationClassLoaderFile($path);
// 如果自动加载文件不存在,交给自动加载不存在的处理函数,跳过本次路径扫描
if (!file_exists($loaderFile)) {
$this->notify('noLoaderFile', $this->clearBasePath($path), $loaderFile);
continue;
}
// 自动加载类存在,则获取 AutoLoader 的带命名空间的完全的类名
$loaderClass = $this->getAnnotationLoaderClassName($ns);
// 检测自动加载类是否存在,防止文件名和类名不一致情况
if (!class_exists($loaderClass)) {
$this->notify('noLoaderClass', $loaderClass);
continue;
}
$isEnabled = true;
$autoLoader = new $loaderClass();
// 检测实例化的自动加载类是否实现类 swoft 要求的自动加载类的接口
if (!$autoLoader instanceof LoaderInterface) {
$this->notify('invalidLoader', $loaderFile);
continue;
}
$this->notify('findLoaderClass', $this->clearBasePath($loaderFile));
// If is disable, will skip scan annotation classes
// 最后再次检查是否是被禁用的自动加载类,或者自动加载类自己配置为不启用
if (isset($this->disabledAutoLoaders[$loaderClass]) || !$autoLoader->isEnable()) {
$isEnabled = false;
$this->notify('disabledLoader', $loaderFile);
} else {
// 正常的类加载器,保存下类加载器对应的文件路径
AnnotationRegister::addAutoLoaderFile($loaderFile);
$this->notify('addLoaderClass', $loaderClass);
// Scan and collect class bean s
// 从一个组件的加载配置中,加载注解相关信息
$this->loadAnnotation($autoLoader);
}
// Storage autoLoader instance to register
// 存储命名空间,自动加载器实例,是否启用信息到注解注册器
AnnotationRegister::addAutoLoader($ns, $autoLoader, $isEnabled);
}
}
}
以下为另一段关键函数:$this->loadAnnotation($autoLoader)
php
/**
* Load annotations from an component loader config.
*
* @param LoaderInterface $loader
*
* @throws AnnotationException
* @throws ReflectionException
*/
private function loadAnnotation(LoaderInterface $loader): void
{
// 获取当前组件的命名空间和目录的绑定关系
$nsPaths = $loader->getPrefixDirs();
// 遍历当前组件命名空间
foreach ($nsPaths as $ns => $path) {
// 递归获取当前目录中的子目录和文件,获取一个迭代器
$iterator = DirectoryHelper::recursiveIterator($path);
/* @var SplFileInfo $splFileInfo */
foreach ($iterator as $splFileInfo) {
// 判断本次迭代的文件到底是文件还是目录,如果是目录则跳过
$filePath = $splFileInfo->getPathname();
// $splFileInfo->isDir();
if (is_dir($filePath)) {
continue;
}
$fileName = $splFileInfo->getFilename();
$extension = $splFileInfo->getExtension();
// 判断当前文件是否是个php文件,不是也跳过
if ($this->loaderClassSuffix !== $extension || strpos($fileName, '.') === 0) {
continue;
}
// It is exclude filename
// 判断当前文件是否是排除的文件,如果要排除,也加到注解注册器中的排除文件列表中
if (isset($this->excludedFilenames[$fileName])) {
AnnotationRegister::addExcludeFilename($fileName);
continue;
}
$suffix = sprintf('.%s', $this->loaderClassSuffix);
$pathName = str_replace([$path, '/', $suffix], ['', '\\', ''], $filePath);
$className = sprintf('%s%s', $ns, $pathName);
// Fix repeat included file bug
// 如果当前文件已经在包含文件列表中
$autoload = in_array($filePath, $this->includedFiles, true);
// Will filtering: interfaces and traits
// 对于没有包含过的文件,通过 class_exists 的方式自动加载到内存
if (!class_exists($className, !$autoload)) {
$this->notify('noExistClass', $className);
continue;
}
// Parse annotation
// 解析当前类的注解信息,保存到注解注册器中
$this->parseAnnotation($ns, $className);
}
}
}
以上过程,通过遍历的方式,将所有的组件进行遍历一遍,这样所有组件中需要包含的类都会加载到内存,相关的注解信息,也都会存储到 AnnotationRegister 注解注册器中。
Bean 进程实例对象处理阶段
此部分主要是关于 BeanFactory 的使用,BeanFactory 的核心部分为对象容器 Container。
BeanFactory 需要收集以下信息,来进行所有 bean 对象的初始化:
- 核心 bean 的定义
- 注解解析对象(Parser)
- 注解对象(Annotation)
- bean 处理器对象
BeanHandler 对象解析
生成一个 BeanHandler 处理对象。此处将 Bean 处理过程抽象化,为的是方便扩展不同的 Bean 处理过程。
BeanHandler 对象中的三个方法:
- beforeInit 方法中,已经将 Aop 切面编程相关的注解信息,跟当前正在处理的 bean 对象进行了绑定,这样,可以让 Bean 后续的实际执行过程中,触发切面中的一些操作。
- classProxy 方法,生成给定类的代理类的相关代码,通过 require 对类进行包含,然后删除生成的代码,再通过 class_exists 方法检测以保证代理类正常加载成功,然后将原类和代理类的关联信息,存放到 Proxy 的 cache 属性中。代理类,生成了类的名字一种为带命名空间的(
$newClassName),一种为只有类名,不带命名空间的($proxyName) - getReferenceValue 方法,获取 config 对象中,给定参数的名字的值
获取核心 Bean 对象定义
核心 bean 对象主要是指:
- 组件 Autoloader 中 beans 方法定义的 bean
- 主项目 app 目录下 bean.php 文件中定义的 bean
将以上 bean 定义的数组进行合并,将核心 Bean 定义传参给 BeanFactory 。优先获取组件中定义的 bean 定义,然后在用 app/bean.php 文件中的定义进行覆盖。所有 bean.php 中的定义优先级要高于组件中的 bean 定义,组件中的 bean 可以认为是默认定义。
注解注册器中获取解析对象和注解对象
将上一个注解信息实例进程中获得的注解相关的信息,注解解析器对象和注解信息对象放入 BeanFactory,用来初始化相关 bean 实例。
在容器的对bean 的实例化代码中,可以看到 parser 是如何和 annotation 工作的。
php
$handler = new BeanHandler();
$definitions = $this->getDefinitions();
$parsers = AnnotationRegister::getParsers();
$annotations = AnnotationRegister::getAnnotations();
BeanFactory::addDefinitions($definitions);
BeanFactory::addAnnotations($annotations);
BeanFactory::addParsers($parsers);
BeanFactory::setHandler($handler);
// 将所有存储到 BeanFactory 的核心bean、注解实例化为 bean 对象,以供取用
BeanFactory::init();
事件进程实例对象处理阶段
获取核心 bean 对象 eventManager,将 eventManager 对象注册到 ListenerRegister 监听器注册对象上。
注册过程:
- 遍历所有监听器,事件管理器逐个添加事件监听器。
- 遍历所有订阅者,事件管理器逐个添加事件订阅者。
php
/** @var EventManager $eventManager */
$eventManager = bean('eventManager');
// 注册事件管理器对象,返回事件管理器添加的监听器和订阅者数量
[$count1, $count2] = ListenerRegister::register($eventManager);
// 打印目前事件管理器关联的 Listener 和 subscriber
CLog::info('Event manager initialized(%d listener, %d subscriber)', $count1, $count2);
// 触发一个 app 启动的事件(这个事件会被关联到事件管理器上的处理者观察到并处理相关业务)
Swoft::trigger(SwoftEvent::APP_INIT_COMPLETE);
这里注意:最初的监听器(Listener)和订阅者(subscriber)的来源,也就是 ListenerRegister 中 $listeners 和 $subscribers属性中的数据,其主要是通过 ListenerRegister::addListener 添加到这两个列表中。通过进一步跟踪,可以看到在 event 组件的 src/Annotation/Parser/ 目录下的 SubscriberParser.php 和 ListenerParser.php 两个注解解析器中看到添加监听的代码,这里就是通过类上的 @listener 和 @subscriber 注解来识别的。如下示例:
php
/**
* Class AppInitCompleteListener
*
* @since 2.0
*
* @Listener(SwoftEvent::APP_INIT_COMPLETE)
*/
在注解进程阶段,这些监听器和事件关系数据,暂时会保存在 ListenerRegister 的两个监听器属性中;在通过 bean 进程阶段后,相关监听器就会实例化,然后存储到 Container 中;再通过事件进程阶段的时候,这些监听器就会在遍历 ListenerRegister 的两个监听器属性的时候,加载到事件管理器中。便于后续系统运行过程中,通过捕捉匹配的事件,来触发相应的监听器来处理。
另外,这里要说一下,这个事件管理器相当于一个自定义的同步事件管理器,就是为了同步处理对应事件。可能后边 http 服务的启动过程中,事件用的 swoole 的事件监听处理,而没有用事件管理处理,也许会让你感到疑惑。其实这个没冲突,swoole 自带自己的事件处理器,所以针对 swoole 已经支持的事件,用他自带的 on 函数,更直接。而对一些 swoole 不支持的事件,也要处理,所以才有了 swoft 的事件管理器。
例如:httpServer 的启动,它的监听器的加载等过程在执行cli 命令后,php bin/swoft http:start ,相当于执行了 HttpServer::class 的 start() 方法,这个过程中,加载了http 服务相关的事件监听。
控制台进程实例对象处理阶段
获取路由实例 cliRouter,将路由注册到命令行注册器。
cliApp 对象在 console 组件的 Autoloader::beans 方法中可以找到相应类,也就是 console 包下的 src/Application.php 文件,查看其中的 run 方法,可以找到 console 命令执行的方式。
php
/** @var Router $router */
$router = bean('cliRouter');
// Register console routes
CommandRegister::register($router);
CLog::info('Console command route registered (group %d, command %d)', $router->groupCount(), $router->count());
// Run console application
if ($this->application->isStartConsole()) {
// 在 console 组件的 Autoloader::beans 方法中可以找到相应类
bean('cliApp')->run();
}
这部分相当于是串行执行的,这里不做更具体的说明。