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