hyperf| hyperf 源碼解讀 1: 啟動(dòng)

date: 2019-08-19 16:47:27
title: hyperf| hyperf 源碼解讀 1: 啟動(dòng)

hyperf 的準(zhǔn)備工作做好后, 就開始運(yùn)行啟動(dòng)命令了:

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.

今天要看這么多內(nèi)容么? 不, 只看這部分:

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

...

這部分就是整個(gè)框架的核心, 這部分搞清楚了, 后面都是搭積木了, 隨用隨取.

PS: 看源碼, 尤其是優(yōu)秀開源項(xiàng)目的源碼, 是程序員進(jìn)階的「終南捷徑」.

入口: bin/hyperf.php

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

use Hyperf\Contract\ApplicationInterface;

// php ini 設(shè)置
ini_set('display_errors', 'on');
ini_set('display_startup_errors', 'on');

error_reporting(E_ALL);

// 定義常量 BASE_PATH, 所有路徑相關(guān)都會(huì)使用這個(gè)常量
!defined('BASE_PATH') && define('BASE_PATH', dirname(__DIR__, 1));

// composer 自動(dòng)加載
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();
})();

很簡(jiǎn)單的幾部分:

  • PHP ini 設(shè)置, 按需設(shè)置即可, 比如這里還可以設(shè)置時(shí)區(qū)
  • 常量 BASE_PATH, hyperf 只設(shè)置了這個(gè)一個(gè)常量, 用來所有 路徑 相關(guān)的場(chǎng)景
  • config/container.php, container 的初始化, 重中之重的內(nèi)容
  • Application->run(), 完整的是 Symfony\Component\Console\Application, 用來跑 cli 應(yīng)用

PS: 有輪子, 而且還很好用, 干嘛非要自己造. 這也是要讀源碼的理由之一.

重點(diǎn): config/container.php

到重點(diǎn)內(nèi)容了, 重要的事情說三遍

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.');
}

// 設(shè)置后, 方便全局獲取 container 實(shí)例
return ApplicationContext::setContainer($container);

使用 composer 提供的工具 ProviderConfig

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

關(guān)鍵是這句, 對(duì)應(yīng)獲取到的 composer.json 文件中的配置:

    "extra": {
        "branch-alias": {
            "dev-master": "1.1-dev"
        },
        // 對(duì)應(yīng)這里的配置
        "hyperf": {
            "config": "Hyperf\\Amqp\\ConfigProvider"
        }
    },

對(duì)應(yīng)的 ConfigProvider 內(nèi)容:

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 部分內(nèi)容:

  • dependencies: 依賴關(guān)系, 解耦神器
  • commands: 部分 hyperf 組件有有自定義的 command, php bin/hyperf.php 看到的命令, 配置就是這里來的
  • scan: 設(shè)置掃描目錄, hyperf 組件是默認(rèn)是組件源碼目錄 src/
  • publish: 通常用來加載組件提供的默認(rèn)配置文件, 或者其他一些組件提供的 demo 文件

container 初始化

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

別看只有一行, 這里干的事情可真不少, 要得到 container 這個(gè) 缺啥都找它要 的神器, 當(dāng)然沒那么簡(jiǎn)單(哼起來~)

關(guān)鍵代碼是這里:

// \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 ...');
        // 關(guān)鍵在這里
        $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;
    }

看起來有點(diǎn)復(fù)雜呀, 別慌, 一言以蔽之, scan 是為了給我們想要的數(shù)據(jù):

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

沒錯(cuò), 注解(Annotation) + Aspect(切面)

container 使用

基于 hyperf 的應(yīng)用中, 缺啥都找 container 就對(duì)了, 具體的文檔可以參考 hyperf doc - 依賴注入

這里補(bǔ)充 2 點(diǎn), 一個(gè)是 container 的補(bǔ)充說明:

// \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 看似復(fù)雜, 最終都會(huì)處理到 container 的 $this->resolvedEntries[$name] 變量里, 不明白的話, 可以把這變量打印一下看一看

第二點(diǎn)對(duì)轉(zhuǎn)到 依賴注入 下的小伙伴說的:

自己 new 出來的變量是無法用到強(qiáng)大的 container 的, 以及之后各類好用的方法, 真愛生命, 不要瞎 new 哦~

寫到最后

hyperf 最核心的部分我們已經(jīng)看到了, 沒錯(cuò), 就是 container, container 在手, 天下我有.

下篇預(yù)告: 依舊從安裝 hyperf 就會(huì)執(zhí)行的命令 php bin/hyperf.php start 入手, 強(qiáng)大的 swoole 在呼喚著我們 !

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市钝鸽,隨后出現(xiàn)的幾起案子寞埠,更是在濱河造成了極大的恐慌,老刑警劉巖鲁僚,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡坞琴,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門逗抑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來剧辐,“玉大人,你說我怎么就攤上這事邮府∮兀” “怎么了?”我有些...
    開封第一講書人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵褂傀,是天一觀的道長(zhǎng)忍啤。 經(jīng)常有香客問我,道長(zhǎng)仙辟,這世上最難降的妖魔是什么同波? 我笑而不...
    開封第一講書人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮叠国,結(jié)果婚禮上未檩,老公的妹妹穿的比我還像新娘。我一直安慰自己粟焊,他們只是感情好冤狡,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著项棠,像睡著了一般悲雳。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上沾乘,一...
    開封第一講書人閱讀 49,046評(píng)論 1 285
  • 那天怜奖,我揣著相機(jī)與錄音,去河邊找鬼翅阵。 笑死歪玲,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的掷匠。 我是一名探鬼主播滥崩,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼讹语!你這毒婦竟也來了钙皮?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎短条,沒想到半個(gè)月后导匣,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡茸时,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年贡定,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片可都。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡缓待,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出渠牲,到底是詐尸還是另有隱情旋炒,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布签杈,位于F島的核電站瘫镇,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏芹壕。R本人自食惡果不足惜汇四,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望踢涌。 院中可真熱鬧通孽,春花似錦、人聲如沸睁壁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽潘明。三九已至行剂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間钳降,已是汗流浹背厚宰。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留遂填,地道東北人铲觉。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像吓坚,于是被迫代替她去往敵國和親撵幽。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容