在看这篇文章的时候,请先阅读注解的实以及AOP的实现,同时对依赖注入有一定的理解。
Hyperf\Di\ClassLoader::init();
上面这行代码,是整个框架的初始化,做的事情非常之多,下面简单过一下流程。
public static function init(?string $proxyFileDirPath = null, ?string $configDir = null): void
{
...
$loaders = spl_autoload_functions();
foreach ($loaders as &$loader) {
$unregisterLoader = $loader;
if (is_array($loader) && $loader[0] instanceof ComposerClassLoader) {
/** @var ComposerClassLoader $composerClassLoader */
$composerClassLoader = $loader[0];
AnnotationRegistry::registerLoader(function ($class) use ($composerClassLoader) {
return (bool) $composerClassLoader->findFile($class);
});
$loader[0] = new static($composerClassLoader, $proxyFileDirPath, $configDir);
}
spl_autoload_unregister($unregisterLoader);
}
unset($loader);
// Re-register the loaders
foreach ($loaders as $loader) {
spl_autoload_register($loader);
}
// Initialize Lazy Loader. This will prepend LazyLoader to the top of autoload queue.
LazyLoader::bootstrap($configDir);
}
首先:替换composer的自动加载函数。其中使用了&loader
引用,并替换$loader[0] = new Static()
下面是composer的自动加载函数注册逻辑。
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
...
}
可以看到,$loader[0] = $this
也就是Composer\Autoload\ClassLoader
所以这里使用$loader[0] = new Static()
也就是使用Hyperf\Di\ClassLoader::loadClass
作为类加载函数
接下来:Composer\Autoload\ClassLoader
的构造函数
// Composer\Autoload\ClassLoader.php
public function __construct(ComposerClassLoader $classLoader, string $proxyFileDir, string $configDir)
{
$this->setComposerClassLoader($classLoader);
if (file_exists(BASE_PATH . '/.env')) {
$this->loadDotenv();
}
// Scan by ScanConfig to generate the reflection class map
$scanner = new Scanner($this, $config = ScanConfig::instance($configDir));
$classLoader->addClassMap($config->getClassMap());
$reflectionClassMap = $scanner->scan();
// Get the class map of Composer loader
$composerLoaderClassMap = $this->getComposerClassLoader()->getClassMap();
$proxyManager = new ProxyManager($reflectionClassMap, $composerLoaderClassMap, $proxyFileDir);
$this->proxies = $proxyManager->getProxies();
}
做的事情非常做。
ScanConfig
会利用ProviderConfig
去解析composer.lock
文件,获取extra
字段下的ConfigProvider
类,进行调用并返回配置数组,以及结合对config
配置文件的合并而生成。
// ScanConfig.php
public static function instance(string $configDir): self
{
if (self::$instance) {
return self::$instance;
}
$configDir = rtrim($configDir, '/');
[$config, $serverDependencies, $cacheable] = static::initConfigByFile($configDir);
return self::$instance = new self(
$cacheable,
$configDir,
$config['paths'] ?? [],
$serverDependencies ?? [],
$config['ignore_annotations'] ?? [],
$config['global_imports'] ?? [],
$config['collectors'] ?? [],
$config['class_map'] ?? []
);
}
private static function initConfigByFile(string $configDir): array
{
...
return [$config, $serverDependencies, $cacheable];
}
再接下来:是Scaner::scan
方法的调用,具体可以查看注解的实现
一文提到。在方法里面是查找并收集所有的注解类和使用注解类的类,以及加载切面。其中为了性能优化,使用文件存储缓存。
最后:代理类proxies
的生成,具体可以查看AOP的实现
一文提到。
use Hyperf\Di\Container;
use Hyperf\Di\Definition\DefinitionSourceFactory;
use Hyperf\Utils\ApplicationContext;
$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
, 当然你也可以替换为自己的容器,前提是实现了\Psr\Container\ContainerInterface
接口。
$application = $container->get(Hyperf\Contract\ApplicationInterface::class);
这里的应用通过从容器里面获取,通过声明一个依赖的摘要abstract
获取最终的concrete
具体实现。对依赖的具体实现,我们可以通过配置文件或者ConfigProvider
进行配置。
通过对代码的搜索,我们发现是通过ConfigProvider
进行了具体实现的配置。
namespace Hyperf\Framework;
use Hyperf\Contract\ApplicationInterface;
use Hyperf\Contract\StdoutLoggerInterface;
use Hyperf\Framework\Logger\StdoutLogger;
class ConfigProvider
{
public function __invoke(): array
{
return [
'dependencies' => [
ApplicationInterface::class => ApplicationFactory::class,
StdoutLoggerInterface::class => StdoutLogger::class,
],
'annotations' => [
'scan' => [
'paths' => [
__DIR__,
],
],
],
];
}
}
为什么会找到ApplicationFactory::class
,我们简单过一下$container->get()
方法
// ApplicationFactory.php
public function get($name)
{
...
$this->make($name);
}
public function make(string $name, array $parameters = [])
{
$definition = $this->getDefinition($name);
if (! $definition) {
throw new NotFoundException("No entry or class found for '{$name}'");
}
return $this->resolveDefinition($definition, $parameters);
}
private function getDefinition(string $name): ?DefinitionInterface
{
// Local cache that avoids fetching the same definition twice
if (! array_key_exists($name, $this->fetchedDefinitions)) {
$this->fetchedDefinitions[$name] = $this->definitionSource->getDefinition($name);
}
return $this->fetchedDefinitions[$name];
}
$this->definitionSource
也就是DefinitionSourceFactory
。
继续跟踪:
class DefinitionSourceFactory
{
public function __invoke()
{
$configDir = $this->baseUri . '/config';
$configFromProviders = [];
if (class_exists(ProviderConfig::class)) {
$configFromProviders = ProviderConfig::load();
}
$serverDependencies = $configFromProviders['dependencies'] ?? [];
if (file_exists($configDir . '/autoload/dependencies.php')) {
$definitions = include $configDir . '/autoload/dependencies.php';
$serverDependencies = array_replace($serverDependencies, $definitions ?? []);
}
return new DefinitionSource($serverDependencies);
}
}
在这里的时候已经可以看到$serverDependencies
,服务依赖是通过配置文件以及ConfigProvider
注册的。
$application->run();
我们已经知道了$application
通过依赖注入得到了具体的类ApplicationFactory
namespace Hyperf\Framework;
use Hyperf\Command\Annotation\Command;
use Hyperf\Contract\ConfigInterface;
use Hyperf\Di\Annotation\AnnotationCollector;
use Hyperf\Framework\Event\BootApplication;
use Psr\Container\ContainerInterface;
use Psr\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Console\Application;
class ApplicationFactory
{
public function __invoke(ContainerInterface $container)
{
if ($container->has(EventDispatcherInterface::class)) {
$eventDispatcher = $container->get(EventDispatcherInterface::class);
$eventDispatcher->dispatch(new BootApplication());
}
$config = $container->get(ConfigInterface::class);
$commands = $config->get('commands', []);
// Append commands that defined by annotation.
$annotationCommands = [];
if (class_exists(AnnotationCollector::class) && class_exists(Command::class)) {
$annotationCommands = AnnotationCollector::getClassesByAnnotation(Command::class);
$annotationCommands = array_keys($annotationCommands);
}
$commands = array_unique(array_merge($commands, $annotationCommands));
$application = new Application();
if (isset($eventDispatcher) && class_exists(SymfonyEventDispatcher::class)) {
$application->setDispatcher(new SymfonyEventDispatcher($eventDispatcher));
}
foreach ($commands as $command) {
$application->add($container->get($command));
}
return $application;
}
}
这里如果看过注解的实现
一文的话,很容易知道这里通过配置文件和注解收集器获取到所有的Command
命令,并添加到Symfony\Component\Console\Application
,熟悉Laravel
或者Symfony
的同学很容易明白了接下来做的事情。这里会根据你的输入的参数去执行相关的命令。
至此,系统流程分析介绍完毕。@todo 服务启动分析。