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 在呼喚着我們 !