您的位置 首页 php

Swoole学习二Hyperf框架启动流程分析

  • 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的启动流程。

  • 启动流程分析

在运行之前我们需要看一下官方文档,了解两个东西

  1. Hyperf的ConfigProvider思想 #/zh-cn/component-guide/configprovider,这是组件化的基础,每个组件需要一个ConfigProvider文件,里面提供依赖处理、目录扫描等配置
  2. 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));
            }
        }
    }  
  • 启动流程图

  • 请求处理

其中框架中的公共组件可以看下:

  1. 路由处理:fastRoute
  2. Console及Command处理:

文章来源:智云一二三科技

文章标题:Swoole学习二Hyperf框架启动流程分析

文章地址:https://www.zhihuclub.com/77108.shtml

关于作者: 智云科技

热门文章

网站地图