hyperf| hyperf 源码解读 1: 启动

date: 2019-08-19 16:47:27
title: hyperf| hyperf 源码解读 1: 启动

hyperf 的准备工作做好后, 就开始运行启动命令了:

php bin/hyperf

可以看到如下输出:

root@820d21e61cd8 /d/hyperf-demo# php bin/hyperf.php
Scanning ...
Scan completed.
[DEBUG] Event Hyperf\Framework\Event\BootApplication handled by Hyperf\Di\Listener\BootApplicationListener listener.
[DEBUG] Event Hyperf\Framework\Event\BootApplication handled by Hyperf\Config\Listener\RegisterPropertyHandlerListener listener.
[DEBUG] Event Hyperf\Framework\Event\BootApplication handled by Hyperf\RpcClient\Listener\AddConsumerDefinitionListener listener.
[DEBUG] Event Hyperf\Framework\Event\BootApplication handled by Hyperf\Paginator\Listener\PageResolverListener listener.
[DEBUG] Event Hyperf\Framework\Event\BootApplication handled by Hyperf\JsonRpc\Listener\RegisterProtocolListener listener.
Console Tool

Usage:
  command [options] [arguments]

Options:
  -h, --help            Display this help message
  -q, --quiet           Do not output any message
  -V, --version         Display this application version
      --ansi            Force ANSI output
      --no-ansi         Disable ANSI output
  -n, --no-interaction  Do not ask any interactive question
  -v|vv|vvv, --verbose  Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug

Available commands:
  help               Displays help for a command
  info               Dump the server info.
  list               Lists commands
  migrate
  start              Start swoole server.
  t                  Hyperf Demo Command
 db
  db:model
 di
  di:init-proxy
 gen
  gen:amqp-consumer  Create a new amqp consumer class
  gen:amqp-producer  Create a new amqp producer class
  gen:aspect         Create a new aspect class
  gen:command        Create a new command class
  gen:controller     Create a new controller class
  gen:job            Create a new job class
  gen:listener       Create a new listener class
  gen:middleware     Create a new middleware class
  gen:migration
  gen:process        Create a new process class
 migrate
  migrate:fresh
  migrate:install
  migrate:refresh
  migrate:reset
  migrate:rollback
  migrate:status
 queue
  queue:flush        Delete all message from failed queue.
  queue:info         Delete all message from failed queue.
  queue:reload       Reload all failed message into waiting queue.
 vendor
  vendor:publish     Publish any publishable configs from vendor packages.

今天要看这么多内容么? 不, 只看这部分:

root@820d21e61cd8 /d/hyperf-demo# php bin/hyperf.php
Scanning ...
Scan completed.

...

这部分就是整个框架的核心, 这部分搞清楚了, 后面都是搭积木了, 随用随取.

PS: 看源码, 尤其是优秀开源项目的源码, 是程序员进阶的「终南捷径」.

入口: bin/hyperf.php

#!/usr/bin/env php
<?php

use Hyperf\Contract\ApplicationInterface;

// php ini 设置
ini_set('display_errors', 'on');
ini_set('display_startup_errors', 'on');

error_reporting(E_ALL);

// 定义常量 BASE_PATH, 所有路径相关都会使用这个常量
!defined('BASE_PATH') && define('BASE_PATH', dirname(__DIR__, 1));

// composer 自动加载
require BASE_PATH . '/vendor/autoload.php';

// Self-called anonymous function that creates its own scope and keep the global namespace clean.
(function () {
    // container
    /** @var \Psr\Container\ContainerInterface $container */
    $container = require BASE_PATH . '/config/container.php';

    // application
    $application = $container->get(ApplicationInterface::class);
    $application->run();
})();

很简单的几部分:

  • PHP ini 设置, 按需设置即可, 比如这里还可以设置时区
  • 常量 BASE_PATH, hyperf 只设置了这个一个常量, 用来所有 路径 相关的场景
  • config/container.php, container 的初始化, 重中之重的内容
  • Application->run(), 完整的是 Symfony\Component\Console\Application, 用来跑 cli 应用

PS: 有轮子, 而且还很好用, 干嘛非要自己造. 这也是要读源码的理由之一.

重点: config/container.php

到重点内容了, 重要的事情说三遍

use Hyperf\Config\ProviderConfig;
use Hyperf\Di\Annotation\Scanner;
use Hyperf\Di\Container;
use Hyperf\Di\Definition\DefinitionSource;
use Hyperf\Utils\ApplicationContext;

// 使用 composer 提供的工具 ProviderConfig 
$configFromProviders = ProviderConfig::load();

// dependency
$definitions = include __DIR__ . '/dependencies.php';
$serverDependencies = array_replace($configFromProviders['dependencies'] ?? [], $definitions['dependencies'] ?? []);

// annotation
$annotations = include __DIR__ . '/autoload/annotations.php';
$scanDirs = $configFromProviders['scan']['paths'];
$scanDirs = array_merge($scanDirs, $annotations['scan']['paths'] ?? []);

// scan
$ignoreAnnotations = $annotations['scan']['ignore_annotations'] ?? ['mixin'];

// container 初始化
$container = new Container(new DefinitionSource($serverDependencies, $scanDirs, new Scanner($ignoreAnnotations)));

if (! $container instanceof \Psr\Container\ContainerInterface) {
    throw new RuntimeException('The dependency injection container is invalid.');
}

// 设置后, 方便全局获取 container 实例
return ApplicationContext::setContainer($container);

使用 composer 提供的工具 ProviderConfig

// \Hyperf\Config\providers
$providers = Composer::getMergedExtra('hyperf')['config'] ?? [];

关键是这句, 对应获取到的 composer.json 文件中的配置:

    "extra": {
        "branch-alias": {
            "dev-master": "1.1-dev"
        },
        // 对应这里的配置
        "hyperf": {
            "config": "Hyperf\\Amqp\\ConfigProvider"
        }
    },

对应的 ConfigProvider 内容:

namespace Hyperf\Amqp;

use Hyperf\Amqp\Packer\Packer;
use Hyperf\Utils\Packer\JsonPacker;

class ConfigProvider
{
    public function __invoke(): array
    {
        return [
            'dependencies' => [
                Producer::class => Producer::class,
                Packer::class => JsonPacker::class,
                Consumer::class => ConsumerFactory::class,
            ],
            'commands' => [
            ],
            'scan' => [
                'paths' => [
                    __DIR__,
                ],
            ],
            'publish' => [
                [
                    'id' => 'config',
                    'description' => 'The config for amqp.',
                    'source' => __DIR__ . '/../publish/amqp.php',
                    'destination' => BASE_PATH . '/config/autoload/amqp.php',
                ],
            ],
        ];
    }
}

这里有 4 部分内容:

  • dependencies: 依赖关系, 解耦神器
  • commands: 部分 hyperf 组件有有自定义的 command, php bin/hyperf.php 看到的命令, 配置就是这里来的
  • scan: 设置扫描目录, hyperf 组件是默认是组件源码目录 src/
  • publish: 通常用来加载组件提供的默认配置文件, 或者其他一些组件提供的 demo 文件

container 初始化

// \Hyperf\Di\Definition\DefinitionSource::__construct
$container = new Container(new DefinitionSource($serverDependencies, $scanDirs, new Scanner($ignoreAnnotations)));

别看只有一行, 这里干的事情可真不少, 要得到 container 这个 缺啥都找它要 的神器, 当然没那么简单(哼起来~)

关键代码是这里:

// \Hyperf\Di\Definition\DefinitionSource::scan
    private function scan(array $paths): bool
    {
        if (empty($paths)) {
            return true;
        }
        $pathsHash = md5(implode(',', $paths));
        if ($this->hasAvailableCache($paths, $pathsHash, $this->cachePath)) {
            $this->printLn('Detected an available cache, skip the scan process.');
            [, $annotationMetadata, $aspectMetadata] = explode(PHP_EOL, file_get_contents($this->cachePath));
            // Deserialize metadata when the cache is valid.
            AnnotationCollector::deserialize($annotationMetadata);
            AspectCollector::deserialize($aspectMetadata);
            return false;
        }
        $this->printLn('Scanning ...');
        // 关键在这里
        $this->scanner->scan($paths);
        $this->printLn('Scan completed.');
        if (! $this->enableCache) {
            return true;
        }
        // enableCache: set cache
        if (! file_exists($this->cachePath)) {
            $exploded = explode('/', $this->cachePath);
            unset($exploded[count($exploded) - 1]);
            $dirPath = implode('/', $exploded);
            if (! is_dir($dirPath)) {
                mkdir($dirPath, 0755, true);
            }
        }
        $data = implode(PHP_EOL, [$pathsHash, AnnotationCollector::serialize(), AspectCollector::serialize()]);
        file_put_contents($this->cachePath, $data);
        return true;
    }

看起来有点复杂呀, 别慌, 一言以蔽之, scan 是为了给我们想要的数据:

AnnotationCollector::serialize()
AspectCollector::serialize()

没错, 注解(Annotation) + Aspect(切面)

container 使用

基于 hyperf 的应用中, 缺啥都找 container 就对了, 具体的文档可以参考 hyperf doc - 依赖注入

这里补充 2 点, 一个是 container 的补充说明:

// \Hyperf\Di\Container::get
    public function get($name)
    {
        // If the entry is already resolved we return it
        if (isset($this->resolvedEntries[$name]) || array_key_exists($name, $this->resolvedEntries)) {
            return $this->resolvedEntries[$name];
        }
        $this->resolvedEntries[$name] = $value = $this->make($name);
        return $value;
    }

上看 scan 看似复杂, 最终都会处理到 container 的 $this->resolvedEntries[$name] 变量里, 不明白的话, 可以把这变量打印一下看一看

第二点对转到 依赖注入 下的小伙伴说的:

自己 new 出来的变量是无法用到强大的 container 的, 以及之后各类好用的方法, 真爱生命, 不要瞎 new 哦~

写到最后

hyperf 最核心的部分我们已经看到了, 没错, 就是 container, container 在手, 天下我有.

下篇预告: 依旧从安装 hyperf 就会执行的命令 php bin/hyperf.php start 入手, 强大的 swoole 在呼唤着我们 !

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容