Skip to content

Latest commit

 

History

History
300 lines (227 loc) · 9.54 KB

系统流程分析.md

File metadata and controls

300 lines (227 loc) · 9.54 KB

系统流程分析

在看这篇文章的时候,请先阅读注解的实以及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 服务启动分析。