- Hyperf简介
这次我们学习下基于swoole的PHP框架的使用,swoole的框架很多,目前主流的有Swoft(swoft.org)、Hyperf(hyperf.io)、EasySwoole(easyswoole.com)。
这里我们以Hyperf为例进行学习,因为大体上这些框架是差不多的,其实就是它有啥我赶紧也弄一个,如果要应用在生产环境上,还需要根据自己的需求进行选择,具体可以看下项目的维护进度、社区的活跃程度等,Hyperf是Swoole官方推荐的框架,开发人员也是swoole的核心人员。
无论是Hyperf还是Laravel、Yii等等,学习一个框架,我们首先要学习人家的官方文档,至少要浏览一遍知道他的基本组成、功能点。
Hyperf提供的功能除了之前php-fpm运行模式下的框架功能如路由、事件、中间件、请求、响应、数据库之外,加入了如注解、微服务组件、全协程支持等基于swoole的功能。
本次我们基于 2.0.3 版本进行学习,不同的版本之前可能略有差异。
我们可以按照文档中的步骤进行安装即可,本机或者docker中,这次我们只看一下Hyperf的启动流程。
- 启动流程分析
在运行之前我们需要看一下官方文档,了解两个东西
- Hyperf的ConfigProvider思想 #/zh-cn/component-guide/configprovider,这是组件化的基础,每个组件需要一个ConfigProvider文件,里面提供依赖处理、目录扫描等配置
- HyperfDi的使用:#/zh-cn/di , 其他传统框架如Laravel也有Container的概念
执行入口文件
./bin/hyperf.php start
hyperf.php
// Self-called anonymous function that creates its own scope and keep the global namespace clean.
(function () {
Hyperf\Di\ClassLoader::init();// 类加载、注解扫描等
/** @var \Psr\Container\ContainerInterface $container */ $container = require BASE_PATH . '/config/container.php';// 初始化容器
// 初始化consoleApplication
$application = $container->get(\Hyperf\Contract\ApplicationInterface::class);
// 启动
$application->run();
})();
在执行前会进行配置的扫描、依赖的处理、注解的扫描等等
通过ClassLoader::init(); 这里面一堆东西,什么类加载、注册、扫描注解、类map等等大家可以慢慢看。
进入到container.php
// (new DefinitionSourceFactory(true))() 这个语法注意,对象当方法调用,会触发__invoke方法
$container = new Container((new DefinitionSourceFactory(true))());
if (! $container instanceof \Psr\Container\ContainerInterface) {
throw new RuntimeException('The dependency injection container is invalid.');
}
return ApplicationContext::setContainer($container);
那么上面DefinitionSourceFactory干的事就是通过上面提到的ConfigProvider来解决依赖的问题,Hyperf的每个组件都需要有ConfigProvider文件,ConfigProvider在启动的时候会进行处理,其中的denpendencies就是处理Inerface与对应的Definition。
那么我们从vendor/hyperf/framework/src/ConfigProvider.php 可以看到ApplicationInterface的实现是ApplicationFactory
- 初始化命令行程序:symfony/console/application ,这是公共组件,Laravel也用的这个
- 解析输入的command: 如 hyperf.php start 得到 vendor/hyperf/server/src/Command/StartServer ,继承自Symfony/console/Command
- 并执行StartServer->execute
- 检查环境 checkEnvironment
- 获取 config/autoload/server.php 配置
- 解析配置
- 根据server里面的type去初始化server
- startServer.php
- 调用vendor/hyperf/server/src/Server->init
- 后面的事情就是初始化server了,type可以是SERVER_HTTP, SERVER_WEBSOCKET等
- 设置on 回调处理如onWorkerStart, onRequest等等
- 这里会扫描路由,包括routes.php及使用注解的路由,组装成url->handleClass的映射,如 api/v1/login -> /UserController->login 类似这样的
- 调用server->start 启动服务, 这里后面就进入到swoole的执行流程了
- 以Http服务为例子,当有请求进来时,会触发onRequest事件
- 也就是vendor/hyperf/http-server/src/Server->onRequest
- 这个流程与其他框架就差不多了
- 获取请求参数,从路由解析出controller->method
- middleware处理
- 最后到达controller进行处理
- 返回response
部分代码:
startServer.php
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->checkEnvironment($output);
$serverFactory = $this->container->get(ServerFactory::class)
->setEventDispatcher($this->container->get(EventDispatcherInterface::class))
->setLogger($this->container->get(StdoutLoggerInterface::class));
// 获取配置 config/autoload/server.php
$serverConfig = $this->container->get(ConfigInterface::class)->get('server', []);
if (! $serverConfig) {
throw new InvalidArgumentException('At least one server should be defined.');
}
// 初始化配置
$serverFactory->configure($serverConfig);
Runtime::enableCoroutine(true, swoole_hook_flags());
// server->start()
$serverFactory->start();
return 0;
}
Server.php
protected function initServers(ServerConfig $config)
{
$servers = $this->sortServers($config->getServers());
// config/autoload/server.php 可以配置多个server
foreach ($servers as $server) {
$name = $server->getName();
$type = $server->getType();
$host = $server->getHost();
$port = $server->getPort();
$sockType = $server->getSockType();
$callbacks = $server->getCallbacks();
if (! $this->server instanceof SwooleServer) {
// 根据类型初始化server 如HTTPServer WebSocketServer
$this->server = $this->makeServer($type, $host, $port, $config->getMode(), $sockType);
$callbacks = array_replace($this->defaultCallbacks(), $config->getCallbacks(), $callbacks);
// 设置回调
$this->registerSwooleEvents($this->server, $callbacks, $name);
$this->server->set(array_replace($config->getSettings(), $server->getSettings()));
ServerManager::add($name, [$type, current($this->server->ports)]);
if (class_exists(BeforeMainServerStart::class)) {
// Trigger BeforeMainServerStart event, this event only trigger once before main server start.
$this->eventDispatcher->dispatch(new BeforeMainServerStart($this->server, $config->toArray()));
}
} else {
/** @var bool|\Swoole\Server\Port $slaveServer */ $slaveServer = $this->server->addlistener($host, $port, $sockType);
if (! $slaveServer) {
throw new \RuntimeException("Failed to listen server port [{$host}:{$port}]");
}
$server->getSettings() && $slaveServer->set(array_replace($config->getSettings(), $server->getSettings()));
$this->registerSwooleEvents($slaveServer, $callbacks, $name);
ServerManager::add($name, [$type, $slaveServer]);
}
// Trigger beforeStart event.
if (isset($callbacks[SwooleEvent::ON_BEFORE_START])) {
[$class, $method] = $callbacks[SwooleEvent::ON_BEFORE_START];
if ($this->container->has($class)) {
$this->container->get($class)->{$method}();
}
}
if (class_exists(BeforeServerStart::class)) {
// Trigger BeforeServerStart event.
$this->eventDispatcher->dispatch(new BeforeServerStart($name));
}
}
}
- 启动流程图
- 请求处理
其中框架中的公共组件可以看下:
- 路由处理:fastRoute
- Console及Command处理: