diff --git a/.vitepress/config.mts b/.vitepress/config.mts index 6bb88eb..b237ebe 100644 --- a/.vitepress/config.mts +++ b/.vitepress/config.mts @@ -13,8 +13,8 @@ export default defineConfig({ tipLabel: '提示', warningLabel: '警告', dangerLabel: '危险', - infoLabel: '信息', - detailsLabel: '详细信息' + infoLabel: 'info', + detailsLabel: '点我查看' }, image: { // 默认禁用图片懒加载 diff --git a/.vitepress/sidebar.ts b/.vitepress/sidebar.ts index e7a3dd3..3d05004 100644 --- a/.vitepress/sidebar.ts +++ b/.vitepress/sidebar.ts @@ -16,6 +16,10 @@ export default [ { text: '路由寻址', link: hyperf + '路由寻址' }, { text: '中间件', link: hyperf + '中间件' }, { text: '响应', link: hyperf + '响应' }, + { text: '附录1 container的get方法', link: hyperf + '附录1 container的get方法' }, + { text: '附录2 注解命令获取', link: hyperf + '附录2 注解命令获取' }, + { text: '附录3 命令注册Application', link: hyperf + '附录3 命令注册Application' }, + { text: '附录4 Application Run方法', link: hyperf + '附录4 Application_Run' }, ] }, { diff --git a/src/hyperf/container.md b/src/hyperf/container.md index 1625f9b..66bd7e8 100644 --- a/src/hyperf/container.md +++ b/src/hyperf/container.md @@ -2,6 +2,7 @@ title: 初始化容器类 --- # 初始化容器类 + 本节分析容器类的实例化过程, ```php @@ -28,10 +29,14 @@ return ApplicationContext::setContainer($container); ``` +::: tip 首先,可以看出,先是实例化了`DefinitionSourceFactory`类,然后使用方法的调用方式调用类,会触发该类的`__invoke`方法,最后返回的内容作为`Container`类的构造函数的参数,然后返回实例化后的容器类。 +::: + +### 生成包含依赖关系和配置的定义源 + +查看`DefinitionSourceFactory`工厂类做了哪些工作, -### DefinitionSourceFactory类 -先看该类做了那些工作, ```php declare(strict_types=1); namespace Hyperf\Di\Definition; @@ -43,7 +48,6 @@ class DefinitionSourceFactory { public function __invoke(): DefinitionSource { - // 常量不存在,报错 if (! defined('BASE_PATH')) { throw new Exception('BASE_PATH is not defined.'); } @@ -51,10 +55,9 @@ class DefinitionSourceFactory // 该方法上面一节分析过,获取所有的服务提供者返回的配置信息数组 $configFromProviders = []; if (class_exists(ProviderConfig::class)) { - $configFromProviders = ProviderConfig::load(); + $configFromProviders = :ProviderConfig:load(); } - // 同理,这里会获取配置文件中的依赖配置信息,如果存在相同的key,则覆盖掉 $serverDependencies = $configFromProviders['dependencies'] ?? []; $dependenciesPath = BASE_PATH . '/config/autoload/dependencies.php'; if (file_exists($dependenciesPath)) { @@ -65,11 +68,69 @@ class DefinitionSourceFactory return new DefinitionSource($serverDependencies); } } +``` + +只有一个方法,也是会被调用的`__invoke`方法,获取依赖信息,并作为参数传递给`DefinitionSource`用作实例化参数。 + +`$serverDependencies`数组具体内容如下, + +:::details + +```php +Array +( + [Psr\SimpleCache\CacheInterface] => Hyperf\Cache\Cache + [Hyperf\Contract\ConfigInterface] => Hyperf\Config\ConfigFactory + [Hyperf\DbConnection\Pool\PoolFactory] => Hyperf\DbConnection\Pool\PoolFactory + [Hyperf\Database\Connectors\ConnectionFactory] => Hyperf\Database\Connectors\ConnectionFactory + [Hyperf\Database\ConnectionResolverInterface] => Hyperf\DbConnection\ConnectionResolver + [db.connector.mysql] => Hyperf\Database\Connectors\MySqlConnector + [Hyperf\Database\Migrations\MigrationRepositoryInterface] => Hyperf\DbConnection\DatabaseMigrationRepositoryFactory + [Hyperf\Di\MethodDefinitionCollectorInterface] => Hyperf\Di\MethodDefinitionCollector + [Hyperf\Di\ClosureDefinitionCollectorInterface] => Hyperf\Di\ClosureDefinitionCollector + [Hyperf\Engine\Contract\Socket\SocketFactoryInterface] => Hyperf\Engine\Socket\SocketFactory + [Psr\EventDispatcher\ListenerProviderInterface] => Hyperf\Event\ListenerProviderFactory + [Psr\EventDispatcher\EventDispatcherInterface] => Hyperf\Event\EventDispatcherFactory + [Hyperf\ExceptionHandler\Formatter\FormatterInterface] => Hyperf\ExceptionHandler\Formatter\DefaultFormatter + [League\Flysystem\Filesystem] => Hyperf\Filesystem\FilesystemInvoker + [Hyperf\Contract\ApplicationInterface] => Hyperf\Framework\ApplicationFactory + [Hyperf\Contract\StdoutLoggerInterface] => Hyperf\Framework\Logger\StdoutLogger + [Hyperf\HttpMessage\Server\RequestParserInterface] => Hyperf\HttpMessage\Server\Request\Parser + [Hyperf\HttpServer\Contract\RequestInterface] => Hyperf\HttpServer\Request + [Hyperf\HttpServer\Contract\ResponseInterface] => Hyperf\HttpServer\Response + [Psr\Http\Message\ServerRequestInterface] => Hyperf\HttpServer\Request + [Psr\Log\LoggerInterface] => Closure Object + ( + [this] => Hyperf\Logger\ConfigProvider Object + ( + ) + + [parameter] => Array + ( + [$container] => + ) + + ) + + [Redis] => Hyperf\Redis\Redis + [Symfony\Component\Serializer\Serializer] => Hyperf\Serializer\SerializerFactory + [Hyperf\Contract\NormalizerInterface] => Hyperf\Serializer\SimpleNormalizer + [Swoole\Server] => Hyperf\Server\SwooleServerFactory + [Hyperf\Contract\TranslatorLoaderInterface] => Hyperf\Translation\FileLoaderFactory + [Hyperf\Contract\TranslatorInterface] => Hyperf\Translation\TranslatorFactory + [Hyperf\Validation\Contract\PresenceVerifierInterface] => Hyperf\Validation\DatabasePresenceVerifierFactory + [Hyperf\Validation\Contract\ValidatorFactoryInterface] => Hyperf\Validation\ValidatorFactoryFactory +) ``` -只有一个方法,也是会被调用的`__invoke`方法。这里返回实例化的`DefinitionSource`对象。 -### DefinitionSource类 + +::: +可以看出,基本上是接口绑定对应的实现类。 + +#### 将抽象接口或类绑定到其对应的实现类或实例 + > `DefinitionSource类`是管理容器中的绑定关系,即将抽象接口或类绑定到其对应的实现类或实例,来看源码。 +> ```php namespace Hyperf\Di\Definition; @@ -85,18 +146,18 @@ use function method_exists; class DefinitionSource implements DefinitionSourceInterface { - protected array $source; + protected array $source; // [!code focus] - public function __construct(array $source) + public function __construct(array $source) // [!code focus:5] { // source 是服务提供者配置文件中的依赖配置信息 $this->source = $this->normalizeSource($source); } /** - * Normalize the user definition source to a standard definition source. + * 将用户定义的源转换为标准源 */ - protected function normalizeSource(array $source): array + protected function normalizeSource(array $source): array // [!code focus:11] { $definitions = []; foreach ($source as $identifier => $definition) { @@ -107,24 +168,25 @@ class DefinitionSource implements DefinitionSourceInterface } return $definitions; } - - /** + /** // [!code focus:23] * 绑定的内容可以是数组、类名、闭包 * @param array|callable|string $definition */ - protected function normalizeDefinition(string $identifier, $definition): ?DefinitionInterface + protected function normalizeDefinition(string $identifier, $definition): ?DefinitionInterface { if ($definition instanceof PriorityDefinition) { $definition = $definition->getDefinition(); } if (is_string($definition) && class_exists($definition)) { + // 如果对应实现类存在__invoke方法,则包装为FactoryDefinition if (method_exists($definition, '__invoke')) { return new FactoryDefinition($identifier, $definition, []); } + // 普通实现则调用autowire自动装配方法 return $this->autowire($identifier, new ObjectDefinition($identifier, $definition)); } - + //如果是闭包,也包装为FactoryDefinition类 if (is_callable($definition)) { return new FactoryDefinition($identifier, $definition, []); } @@ -134,7 +196,7 @@ class DefinitionSource implements DefinitionSourceInterface /** * 根据类名自动完成构造函数的依赖注入 */ - protected function autowire(string $name, ObjectDefinition $definition = null): ?ObjectDefinition + protected function autowire(string $name, ObjectDefinition $definition = null): ?ObjectDefinition // [!code focus:21] { $className = $definition ? $definition->getClassName() : $name; if (! class_exists($className) && ! interface_exists($className)) { @@ -143,9 +205,7 @@ class DefinitionSource implements DefinitionSourceInterface $definition = $definition ?: new ObjectDefinition($name); - /** - * Constructor. - */ + // 获取类反射,根据类的构造函数,自动注入依赖 $class = ReflectionManager::reflectClass($className); $constructor = $class->getConstructor(); if ($constructor && $constructor->isPublic()) { @@ -158,24 +218,32 @@ class DefinitionSource implements DefinitionSourceInterface } ``` -> `normalizeSource`方法会将绑定的内容进行标准化,标准化后的内容是`DefinitionInterface`接口的实现类。(意思就是将绑定的`key`和`value`转换成`DefinitionInterface`接口的实现类) -最终,将绑定和对应的实现类保存到`$this->source`属性中。 +::: tip +`normalizeSource`方法会将绑定的内容进行标准化,标准化后的内容是`DefinitionInterface`接口的实现类。(意思就是将绑定的`key`和`value`转换成`DefinitionInterface`接口的实现类) +::: -实际上,这里`(new DefinitionSourceFactory())()`执行过后返回的是一个`DefinitionSource`对象,该对象中保存了所有的绑定关系。 +最终,将绑定和对应的实现类保存到`$this->source`属性中,返回自身实例。 + +> 实际上,这里`(new DefinitionSourceFactory())()`执行过后返回的是一个`DefinitionSource`对象,该对象中保存了所有的绑定关系。 + +### Container实例化 + +容器参数上面已经初始化完成,接下来进行容器的实例化。 -然后继续往下看, ```php $container = new Container((new DefinitionSourceFactory())()); ``` -> 这里返回`DefinitionSource`对象后,再调用`new Container`方法,传入`DefinitionSource`对象,返回`Container`对象。 + +> 参数是`DefinitionSource`对象 查看`Container`类的构造函数源码, + ```php public function __construct(protected Definition\DefinitionSourceInterface $definitionSource) { $this->definitionResolver = new ResolverDispatcher($this); - // Auto-register the container. + // 将自身实例放到已解析绑定的数组中 $this->resolvedEntries = [ self::class => $this, PsrContainerInterface::class => $this, @@ -183,15 +251,124 @@ public function __construct(protected Definition\DefinitionSourceInterface $defi ]; } ``` -总结: + +构造函数所做的事情: + 1. 将传入的`$definitionSource`对象放到`$this->definitionSource`属性中。 2. 创建`ResolverDispatcher`解析器,调度不同类型的解析器来处理容器中的定义,它会根据定义的类型(如类、工厂函数等)来选择适当的解析器进行解析 -3. 注册容器本身的实例 +::: details + +```php +namespace Hyperf\Di\Resolver; + +use Hyperf\Di\Definition\DefinitionInterface; +use Hyperf\Di\Definition\FactoryDefinition; +use Hyperf\Di\Definition\ObjectDefinition; +use Hyperf\Di\Definition\SelfResolvingDefinitionInterface; +use Hyperf\Di\Exception\InvalidDefinitionException; +use Psr\Container\ContainerInterface; +use RuntimeException; + +class ResolverDispatcher implements ResolverInterface +{ + protected ?ObjectResolver $objectResolver = null; + + protected ?FactoryResolver $factoryResolver = null; + // 实例化传入容器对象 + public function __construct(private ContainerInterface $container) // [!code focus:3] + { + } + + /** + * 解析definition对应的类实现或执行闭包 + * + * @param DefinitionInterface $definition object that defines how the value should be obtained + * @param array $parameters optional parameters to use to build the entry + * @return mixed value obtained from the definition + * @throws InvalidDefinitionException if the definition cannot be resolved + */ + public function resolve(DefinitionInterface $definition, array $parameters = []) // [!code focus:15] + { + if ($definition instanceof SelfResolvingDefinitionInterface) { + return $definition->resolve($this->container); + } + + $guard = DepthGuard::getInstance(); + + return $guard->call( + $definition->getName(), + fn () => $this->getDefinitionResolver($definition)->resolve($definition, $parameters) + ); + } + + /** + * 判断一个definition能否解析 + * + * @param DefinitionInterface $definition object that defines how the value should be obtained + * @param array $parameters optional parameters to use to build the entry + */ + public function isResolvable(DefinitionInterface $definition, array $parameters = []): bool + { + if ($definition instanceof SelfResolvingDefinitionInterface) { + return $definition->isResolvable($this->container); + } + + $guard = DepthGuard::getInstance(); + + return $guard->call( + $definition->getName(), + fn () => $this->getDefinitionResolver($definition)->isResolvable($definition, $parameters) + ); + } + + /** + * 根据definition类型,返回要使用的解析器 + * + * @throws RuntimeException no definition resolver was found for this type of definition + */ + private function getDefinitionResolver(DefinitionInterface $definition): ResolverInterface // [!code focus:8] + { + return match (true) { + $definition instanceof ObjectDefinition => $this->objectResolver ??= new ObjectResolver($this->container, $this), + $definition instanceof FactoryDefinition => $this->factoryResolver ??= new FactoryResolver($this->container, $this), + default => throw new RuntimeException('No definition resolver was configured for definition of type ' . get_class($definition)), + }; + } +} + +``` + +::: +3. 注册容器本身的实例 +::: details + +```php +$this->resolvedEntries = [ + self::class => $this, + PsrContainerInterface::class => $this, + HyperfContainerInterface::class => $this, + ]; +``` + +::: +到此,容器实例化完成。 + +### ApplicationContext 类 + +```php +$container = new Container((new DefinitionSourceFactory())()); + +return ApplicationContext::setContainer($container); // [!code focus] +``` + +最后,将容器实例保存到`ApplicationContext`类的属性中。 +后续在代码中获取容器类可以使用`ApplicationContext::getContainer()`方法获取容器实例。 + +## 总结 -### 总结 1. 首先创建一个`DefinitionSourceFactory`工厂实例,并且触发该工厂实例的`__invoke`方法 2. `__invoke`方法中,首先加载配置文件包括类映射和依赖配置 3. 跟`config`目录下`dependencies.php`文件中的依赖配置进行合并 4. 创建`DefinitionSource`对象,传入依赖配置信息,负责管理容器中的绑定关系和依赖注入的定义 5. 实例化容器对象,将`DefinitionSource`对象传入 -6. 构造函数中实例化`ResolverDispatcher`调度器作为容器类的属性,并注册容器本身的实例 \ No newline at end of file +6. 构造函数中实例化`ResolverDispatcher`调度器作为容器类的属性,并注册容器本身的实例 diff --git a/src/hyperf/启动服务.md b/src/hyperf/启动服务.md index 3e01fb3..fdddd21 100644 --- a/src/hyperf/启动服务.md +++ b/src/hyperf/启动服务.md @@ -1,291 +1,265 @@ --- -title: $application实例化 +title: 启动服务 --- +# 启动服务 + 这节分析入口文件最后两行代码, + ```php -$application = $container->get(Hyperf\Contract\ApplicationInterface::class); -$application->run(); +(function () { + Hyperf\Di\ClassLoader::init(); + /** @var Psr\Container\ContainerInterface $container */ + $container = require BASE_PATH . '/config/container.php'; + + $application = $container->get(Hyperf\Contract\ApplicationInterface::class); // [!code focus:2] + $application->run(); +})(); ``` -从容器中获取`ApplicationInterface`实例,然后调用`run`方法启动服务。 -### $application是什么 +::: tip +简单来说,这段代码是从依赖注入容器中获取`ApplicationInterface`接口对应的实例,然后调用`run`方法启动服务。 +::: + +## 生成Application + +从上一篇实例化`container`中可知,其中`ApplicationInterface`对应的是`Hyperf\Framework\ApplicationFactory`类。 -从之前的分析可知,`$container`是容器类,从容器中解析出`ApplicationInterface`实例。 ![](https://oss.xiaokeaii.top/2024/applicationInterface.png) -该接口对应的是`Hyperf\Framework\ApplicationFactory`类。分析一下取出源码, -```php -public function get($id) -{ - // 如果解析过,则直接返回 - if (isset($this->resolvedEntries[$id]) || array_key_exists($id, $this->resolvedEntries)) { - return $this->resolvedEntries[$id]; - } - return $this->resolvedEntries[$id] = $this->make($id); -} -``` -上面可以看出,首次解析不走`if`,查看`make`方法, -```php -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); -} -``` -前面绑定阶段,已经将`ApplicationInterface`绑定到`Hyperf\Framework\ApplicationFactory`类上,通过`DefinitionSource`类,将对应实现转换成对应`DefinitionInterface`接口的实现类。 -```php -// 调用逻辑 -public function __construct(array $source) -{ - $this->source = $this->normalizeSource($source); -} +::: warning +具体`get`取出过程,请参考 [附录1](./附录1%20container的get方法){target="_blank"} +::: -protected function normalizeSource(array $source): array -{ - $definitions = []; - foreach ($source as $identifier => $definition) { - $normalizedDefinition = $this->normalizeDefinition($identifier, $definition); - if (! is_null($normalizedDefinition)) { - $definitions[$identifier] = $normalizedDefinition; - } - } - return $definitions; -} - -protected function normalizeDefinition(string $identifier, $definition): ?DefinitionInterface -{ - if ($definition instanceof PriorityDefinition) { - $definition = $definition->getDefinition(); - } - - - if (is_string($definition) && class_exists($definition)) { - if (method_exists($definition, '__invoke')) { - return new FactoryDefinition($identifier, $definition, []); - } - return $this->autowire($identifier, new ObjectDefinition($identifier, $definition)); - } - - if (is_callable($definition)) { - return new FactoryDefinition($identifier, $definition, []); - } - - return null; -} -``` -查看`ApplicationFactory`类,存在`__invoke`方法,返回的是一个`FactoryDefinition`对象。 -![](image https://oss.xiaokeaii.top/2024/applicationFactory.png) +通过分析`get`方法可知,最终调用`ApplicationFactory`类的`__invoke`方法,获得解析后的对象。 ```php -$definition = $this->getDefinition($name); -``` -所以这行返回的是一个`FactoryDefinition`对象。打印结果如下, -![](image https://oss.xiaokeaii.top/2024/工厂定义.png ) +namespace Hyperf\Framework; -#### resolveDefinition方法 -继续分析,查看`resolveDefinition`方法, -> 文件位置: /vendor/hyperf/di/src/Container.php -```php -protected function resolveDefinition(DefinitionInterface $definition, array $parameters = []) -{ - return $this->definitionResolver->resolve($definition, $parameters); -} -``` -上面可以看到,这里调用了解析调度器`ResolverDispatcher`的`resolve`方法。 - -##### resolve方法 -> 文件位置:/vendor/hyperf/di/src/Resolver/ResolverDispatcher.php -```php -public function resolve(DefinitionInterface $definition, array $parameters = []) -{ - if ($definition instanceof SelfResolvingDefinitionInterface) { - - return $definition->resolve($this->container); - } - $guard = DepthGuard::getInstance(); - - return $guard->call( - $definition->getName(), - fn () => $this->getDefinitionResolver($definition)->resolve($definition, $parameters) - ); -} -``` -这里`FactoryDefinition`不是`SelfResolvingDefinitionInterface`接口的实现类,所以不走`if`。 -`$guard`是`DepthGuard`类的实例,调用`call`方法, -###### call方法 -> 文件位置:/vendor/hyperf/di/src/Resolver/DepthGuard.php -```php -public function call(string $name, callable $callable) -{ - try { - $this->increment(); - return $callable(); - } catch (CircularDependencyException $exception) { - $exception->addDefinitionName($name); - throw $exception; - } finally { - $this->decrement(); - } -} -``` -这里最终执行传入的闭包,回到上面,执行闭包。 -查看闭包执行源码, -> ./vendor/hyperf/di/src/Resolver/ResolverDispatcher.php -##### getDefinitionResolver方法 -```php -private function getDefinitionResolver(DefinitionInterface $definition): ResolverInterface -{ - return match (true) { - $definition instanceof ObjectDefinition => $this->objectResolver ??= new ObjectResolver($this->container, $this), - $definition instanceof FactoryDefinition => $this->factoryResolver ??= new FactoryResolver($this->container, $this), - default => throw new RuntimeException('No definition resolver was configured for definition of type ' . get_class($definition)), - }; -} -``` -从代码可知,这里会根据`DefinitionInterface`的实现类,将对应解析器赋值给成员变量。 -这里传进来的是`FactoryDefinition`类,所以会执行第二条。 -紧接着调用`FactoryResolver`类的`resolve`方法。 -###### resolve方法 -> 文件位置:/vendor/hyperf/di/src/Resolver/FactoryResolver.php -```php -public function resolve(DefinitionInterface $definition, array $parameters = []) -{ - $callable = null; - try { - $callable = $definition->getFactory(); - if (! method_exists($callable, '__invoke')) { - throw new NotCallableException(); - } - if (is_string($callable)) { - $callable = $this->container->get($callable); - } - return $callable($this->container, $parameters); - } catch (NotCallableException $e) { - // Custom error message to help debugging - if (is_string($callable) && class_exists($callable) && method_exists($callable, '__invoke')) { - throw new InvalidDefinitionException(sprintf('Entry "%s" cannot be resolved: factory %s. Invokable classes cannot be automatically resolved if autowiring is disabled on the container, you need to enable autowiring or define the entry manually.', $definition->getName(), $e->getMessage())); - } - - throw new InvalidDefinitionException(sprintf('Entry "%s" cannot be resolved: factory %s', $definition->getName(), $e->getMessage())); - } -} -``` -分析该方法,`$callable`是工厂的类名,如果传递过来的是字符串,则通过容器解析出对应的类,方法同理,不再赘述。 -最终调用该工厂类的`__invoke`方法返回解析结果。 - -### ApplicationFactory类 -兜兜转转一圈,最终调用`ApplicationFactory`类的`__invoke`方法,获得解析后的对象。 -```php +use Hyperf\Command\Annotation\Command; +use Hyperf\Command\Parser; +use Hyperf\Contract\ConfigInterface; +use Hyperf\Di\Annotation\AnnotationCollector; +use Hyperf\Framework\Event\BootApplication; +use InvalidArgumentException; +use Psr\Container\ContainerInterface; +use Psr\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Console\Application; -public function __invoke(ContainerInterface $container) +use Symfony\Component\Console\Command\Command as SymfonyCommand; +use Symfony\Component\Console\Exception\InvalidArgumentException as SymfonyInvalidArgumentException; +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputOption; + +class ApplicationFactory { - if ($container->has(EventDispatcherInterface::class)) { - $eventDispatcher = $container->get(EventDispatcherInterface::class); - $eventDispatcher->dispatch(new BootApplication()); + public function __invoke(ContainerInterface $container) + { + // 获取事件调度器,并触发BootApplication事件 + if ($container->has(EventDispatcherInterface::class)) { + $eventDispatcher = $container->get(EventDispatcherInterface::class); + $eventDispatcher->dispatch(new BootApplication()); + } + // 获取配置文件ConfigInterface的接口实现类 + $config = $container->get(ConfigInterface::class); + $commands = $config->get('commands', []); + // 从注解中获取命令 // [!code focus:6] + $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类 // [!code focus:2] + $application = new Application(); + // 如果启用 Symfony 事件,设置事件调度器 + if ($config->get('symfony.event.enable', false) && isset($eventDispatcher) && class_exists(SymfonyEventDispatcher::class)) { + $application->setDispatcher(new SymfonyEventDispatcher($eventDispatcher)); + } + // 添加命令到应用Application中 // [!code focus:8] + foreach ($commands as $command) { + $application->add( + $this->pendingCommand($container->get($command)) + ); + } + + return $application; } - $config = $container->get(ConfigInterface::class); - $commands = $config->get('commands', []); - // 从注解中收集命令 - $annotationCommands = []; - if (class_exists(AnnotationCollector::class) && class_exists(Command::class)) { - $annotationCommands = AnnotationCollector::getClassesByAnnotation(Command::class); - $annotationCommands = array_keys($annotationCommands); - } + /** + * @throws InvalidArgumentException + * @throws SymfonyInvalidArgumentException + * @throws LogicException + */ + protected function pendingCommand(SymfonyCommand $command): SymfonyCommand + { + /** @var null|Command $annotation */ + $annotation = AnnotationCollector::getClassAnnotation($command::class, Command::class) ?? null; - $commands = array_unique(array_merge($commands, $annotationCommands)); - $application = new Application(); + if (! $annotation) { + return $command; + } - if ($config->get('symfony.event.enable', false) && isset($eventDispatcher) && class_exists(SymfonyEventDispatcher::class)) { - $application->setDispatcher(new SymfonyEventDispatcher($eventDispatcher)); - } - // 将命令注册到应用中,下面会用到。 - foreach ($commands as $command) { - $application->add( - $this->pendingCommand($container->get($command)) - ); - } + if ($annotation->signature) { + [$name, $arguments, $options] = Parser::parse($annotation->signature); + if ($name) { + $annotation->name = $name; + } + if ($arguments) { + $annotation->arguments = array_merge($annotation->arguments, $arguments); + } + if ($options) { + $annotation->options = array_merge($annotation->options, $options); + } + } - return $application; + if ($annotation->name) { + $command->setName($annotation->name); + } + + if ($annotation->arguments) { + $annotation->arguments = array_map(static function ($argument): InputArgument { + if ($argument instanceof InputArgument) { + return $argument; + } + + if (is_array($argument)) { + return new InputArgument(...$argument); + } + + throw new LogicException(sprintf('Invalid argument type: %s.', gettype($argument))); + }, $annotation->arguments); + + $command->getDefinition()->addArguments($annotation->arguments); + } + + if ($annotation->options) { + $annotation->options = array_map(static function ($option): InputOption { + if ($option instanceof InputOption) { + return $option; + } + + if (is_array($option)) { + return new InputOption(...$option); + } + + throw new LogicException(sprintf('Invalid option type: %s.', gettype($option))); + }, $annotation->options); + + $command->getDefinition()->addOptions($annotation->options); + } + + if ($annotation->description) { + $command->setDescription($annotation->description); + } + + if ($annotation->aliases) { + $command->setAliases($annotation->aliases); + } + + return $command; + } } ``` + +::: warning +从注解中获取命令的具体实现,请参考[附录2](./附录2%20注解命令获取){target="_blank"} +::: +获取到的`$commands`命令数组如下所示, +:::details + +```php + Array +( + [0] => Hyperf\Server\Command\StartServer + [1] => Hyperf\Database\Commands\ModelCommand + [2] => Hyperf\Database\Commands\Migrations\GenMigrateCommand + [3] => Hyperf\Database\Commands\Seeders\GenSeederCommand + [4] => Hyperf\Database\Commands\Migrations\InstallCommand + [5] => Hyperf\Database\Commands\Migrations\MigrateCommand + [6] => Hyperf\Database\Commands\Migrations\FreshCommand + [7] => Hyperf\Database\Commands\Migrations\RefreshCommand + [8] => Hyperf\Database\Commands\Migrations\ResetCommand + [9] => Hyperf\Database\Commands\Migrations\RollbackCommand + [10] => Hyperf\Database\Commands\Migrations\StatusCommand + [11] => Hyperf\Database\Commands\Seeders\SeedCommand + [12] => Hyperf\Watcher\Command\WatchCommand + [13] => Qbhy\HyperfAuth\AuthCommand + [14] => Hyperf\Devtool\InfoCommand + [15] => Hyperf\Devtool\Describe\ListenersCommand + [16] => Hyperf\Devtool\Describe\RoutesCommand + [17] => Hyperf\Devtool\Describe\AspectsCommand + [18] => Hyperf\Devtool\Generator\CommandCommand + [19] => Hyperf\Devtool\Generator\NatsConsumerCommand + [20] => Hyperf\Devtool\Generator\ListenerCommand + [21] => Hyperf\Devtool\Generator\NsqConsumerCommand + [22] => Hyperf\Devtool\Generator\AspectCommand + [23] => Hyperf\Devtool\Generator\JobCommand + [24] => Hyperf\Devtool\Generator\ResourceCommand + [25] => Hyperf\Devtool\Generator\ClassCommand + [26] => Hyperf\Devtool\Generator\ControllerCommand + [27] => Hyperf\Devtool\Generator\AmqpProducerCommand + [28] => Hyperf\Devtool\Generator\AmqpConsumerCommand + [29] => Hyperf\Devtool\Generator\ProcessCommand + [30] => Hyperf\Devtool\Generator\KafkaConsumerCommand + [31] => Hyperf\Devtool\Generator\MiddlewareCommand + [32] => Hyperf\Devtool\Generator\ConstantCommand + [33] => Hyperf\Devtool\Generator\RequestCommand + [34] => Hyperf\Devtool\VendorPublishCommand + [35] => App\Command\InitCommand +) +``` + +::: 从上面代码可以看出,返回的是一个`Symfony\Component\Console\Application`对象。 上面代码做了三件事: + 1. 首先从容器中取出事件调度器,并触发`BootApplication`事件。 2. 创建`Application`应用对象,从注解和配置文件中获取所有命令。 +::: tip +`Symfony\Component\Console\Application` 类是 `Symfony` 控制台组件的核心部分,用于创建和管理 `CLI` 应用程序。它提供了一系列功能,使开发者可以轻松地定义和运行命令行工具 +::: 3. 将命令注册到应用中。 -#### add方法 -查看`Application`类的`add`方法, +::: warning +从命令注册到`Application`的具体实现,请参考[附录3](./附录3%20命令注册Application){target="_blank"} +::: ```php -public function add(Command $command) -{ - $this->init(); - - $command->setApplication($this); - - if (!$command->isEnabled()) { - $command->setApplication(null); - - return null; - } - - if (!$command instanceof LazyCommand) { - // Will throw if the command is not correctly initialized. - $command->getDefinition(); - } - - if (!$command->getName()) { - throw new LogicException(sprintf('The command defined in "%s" cannot have an empty name.', get_debug_type($command))); - } - // 重点看这行,将命令的名称以及对应实现添加到命令属性中 - $this->commands[$command->getName()] = $command; - - foreach ($command->getAliases() as $alias) { - $this->commands[$alias] = $command; - } - return $command; -} -``` -打印配置的命令数组,打开第一个`/vendor/hyperf/server/src/Command/StartServer.php`命令类, -![](image https://oss.xiaokeaii.top/2024/start.png) -> 我们可以发现,在父类的构造方法中,会将`name`赋值给该命令类的成员变量,之后通过`getName`方法获取命令的名称 -```php -public function __construct(private ContainerInterface $container) -{ - parent::__construct('start'); - $this->setDescription('Start hyperf servers.'); -} -// StartServer的父类的构造方法 -public function __construct(?string $name = null) -{ - $this->definition = new InputDefinition(); - //... - if (null !== $name) { - $this->setName($name); - } - //... -} - -public function getName(): ?string -{ - return $this->name; +foreach ($commands as $command) { + $application->add( + $this->pendingCommand($container->get($command)) + ); } ``` -### run启动 +4. 返回`Application`实例 + +```php +return $application; +``` + +## run启动 + +启动服务, > 文件位置:/bin/hyperf.php + ```php -$application->run(); +(function () { + Hyperf\Di\ClassLoader::init(); + /** @var Psr\Container\ContainerInterface $container */ + $container = require BASE_PATH . '/config/container.php'; + + $application = $container->get(Hyperf\Contract\ApplicationInterface::class); + $application->run(); // [!code focus] +})(); ``` + 从上面分析可知,`$application`是一个`Symfony\Component\Console\Application`对象,调用`run`方法。 先看源码, + ```php public function run(?InputInterface $input = null, ?OutputInterface $output = null): int { @@ -317,7 +291,8 @@ public function run(?InputInterface $input = null, ?OutputInterface $output = nu $this->configureIO($input, $output); try { - $exitCode = $this->doRun($input, $output); + // 执行命令 // [!code focus:2] + $exitCode = $this->doRun($input, $output); } catch (\Throwable $e) { if ($e instanceof \Exception && !$this->catchExceptions) { throw $e; @@ -363,317 +338,18 @@ public function run(?InputInterface $input = null, ?OutputInterface $output = nu return $exitCode; } - -``` -#### doRun方法 -> 文件位置:/vendor/symfony/console/Application.php -```php -public function doRun(InputInterface $input, OutputInterface $output) -{ - // 输出版本信息 - if (true === $input->hasParameterOption(['--version', '-V'], true)) { - $output->writeln($this->getLongVersion()); - - return 0; - } - - try { - // Makes ArgvInput::getFirstArgument() able to distinguish an option from an argument. - $input->bind($this->getDefinition()); - } catch (ExceptionInterface) { - // Errors must be ignored, full binding/validation happens later when the command is known. - } - - // 获取命令名称,项目启动代码是 php bin/hyperf.php start, - // 所以命令名称就是 start - $name = $this->getCommandName($input); - // 如果命令名称是--help,则显示帮助信息 - if (true === $input->hasParameterOption(['--help', '-h'], true)) { - if (!$name) { - $name = 'help'; - $input = new ArrayInput(['command_name' => $this->defaultCommand]); - } else { - $this->wantHelps = true; - } - } - // 如果命令名称不存在,则显示默认信息 - if (!$name) { - $name = $this->defaultCommand; - $definition = $this->getDefinition(); - $definition->setArguments(array_merge( - $definition->getArguments(), - [ - 'command' => new InputArgument('command', InputArgument::OPTIONAL, $definition->getArgument('command')->getDescription(), $name), - ] - )); - } - - try { - $this->runningCommand = null; - // 从应用中找到对应的命令 - $command = $this->find($name); - } catch (\Throwable $e) { - // 找不到的时候的异常处理 - if (($e instanceof CommandNotFoundException && !$e instanceof NamespaceNotFoundException) && 1 === \count($alternatives = $e->getAlternatives()) && $input->isInteractive()) { - $alternative = $alternatives[0]; - - $style = new SymfonyStyle($input, $output); - $output->writeln(''); - $formattedBlock = (new FormatterHelper())->formatBlock(sprintf('Command "%s" is not defined.', $name), 'error', true); - $output->writeln($formattedBlock); - if (!$style->confirm(sprintf('Do you want to run "%s" instead? ', $alternative), false)) { - if (null !== $this->dispatcher) { - $event = new ConsoleErrorEvent($input, $output, $e); - $this->dispatcher->dispatch($event, ConsoleEvents::ERROR); - - return $event->getExitCode(); - } - - return 1; - } - - $command = $this->find($alternative); - } else { - if (null !== $this->dispatcher) { - $event = new ConsoleErrorEvent($input, $output, $e); - $this->dispatcher->dispatch($event, ConsoleEvents::ERROR); - - if (0 === $event->getExitCode()) { - return 0; - } - - $e = $event->getError(); - } - - try { - if ($e instanceof CommandNotFoundException && $namespace = $this->findNamespace($name)) { - $helper = new DescriptorHelper(); - $helper->describe($output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output, $this, [ - 'format' => 'txt', - 'raw_text' => false, - 'namespace' => $namespace, - 'short' => false, - ]); - - return isset($event) ? $event->getExitCode() : 1; - } - - throw $e; - } catch (NamespaceNotFoundException) { - throw $e; - } - } - } - // 如果commend继承LazyCommand类,则属于懒加载命令 - // 延迟实例化命令对象,以提高应用程序启动速度 - if ($command instanceof LazyCommand) { - $command = $command->getCommand(); - } - // 将运行的命令赋值给runningCommand属性 - $this->runningCommand = $command; - // 执行命令 - $exitCode = $this->doRunCommand($command, $input, $output); - $this->runningCommand = null; - - return $exitCode; -} ``` -##### doRunCommand方法 -这里正式进行命令的执行操作; 我们的启动命令是`start`,我们就以`start`命令为例,该命令对应的类是`StartServer`。 +调用`run`方法后,会由`Hyperf`的`StartServer`的命令类接管,执行该类的`execute`方法。 -> 文件位置:/vendor/symfony/console/Application.php -```php -protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output) -{ - // 循环命令的帮助器集合,初始化时未配置,默认是有4个 - // return new HelperSet([ - // new FormatterHelper(), - // new DebugFormatterHelper(), - // new ProcessHelper(), - // new QuestionHelper(), - // ]); - foreach ($command->getHelperSet() as $helper) { - if ($helper instanceof InputAwareInterface) { - $helper->setInput($input); - } - } - // 获取当前命令是否实现了`SignalableCommandInterface`接口,如果是,获取订阅的信号列表 - // $commandSignals = $command instanceof SignalableCommandInterface ? $command->getSubscribedSignals() : []; - // 检查是否存在需要处理的信号或者事件,$commandSignals初始化时为空,不处理信号 - // if ($commandSignals || $this->dispatcher && $this->signalsToDispatchEvent) { - // if (!$this->signalRegistry) { - // throw new RuntimeException('Unable to subscribe to signal events. Make sure that the "pcntl" extension is installed and that "pcntl_*" functions are not disabled by your php.ini\'s "disable_functions" directive.'); - // } +::: tip +`run`方法的具体实现请参考[附录4](./附录4%20Application_Run){target="_blank"} +::: - // if (Terminal::hasSttyAvailable()) { - // $sttyMode = shell_exec('stty -g'); +### StartServer类 - // foreach ([\SIGINT, \SIGTERM] as $signal) { - // $this->signalRegistry->register($signal, static fn () => shell_exec('stty '.$sttyMode)); - // } - // } - - // if ($this->dispatcher) { - // // We register application signals, so that we can dispatch the event - // foreach ($this->signalsToDispatchEvent as $signal) { - // $event = new ConsoleSignalEvent($command, $input, $output, $signal); - - // $this->signalRegistry->register($signal, function ($signal) use ($event, $command, $commandSignals) { - // $this->dispatcher->dispatch($event, ConsoleEvents::SIGNAL); - // $exitCode = $event->getExitCode(); - - // // If the command is signalable, we call the handleSignal() method - // if (\in_array($signal, $commandSignals, true)) { - // $exitCode = $command->handleSignal($signal, $exitCode); - // // BC layer for Symfony <= 5 - // if (null === $exitCode) { - // trigger_deprecation('symfony/console', '6.3', 'Not returning an exit code from "%s::handleSignal()" is deprecated, return "false" to keep the command running or "0" to exit successfully.', get_debug_type($command)); - // $exitCode = 0; - // } - // } - - // if (false !== $exitCode) { - // $event = new ConsoleTerminateEvent($command, $event->getInput(), $event->getOutput(), $exitCode, $signal); - // $this->dispatcher->dispatch($event, ConsoleEvents::TERMINATE); - - // exit($event->getExitCode()); - // } - // }); - // } - - // // then we register command signals, but not if already handled after the dispatcher - // $commandSignals = array_diff($commandSignals, $this->signalsToDispatchEvent); - // } - - // foreach ($commandSignals as $signal) { - // $this->signalRegistry->register($signal, function (int $signal) use ($command): void { - // $exitCode = $command->handleSignal($signal); - // // BC layer for Symfony <= 5 - // if (null === $exitCode) { - // trigger_deprecation('symfony/console', '6.3', 'Not returning an exit code from "%s::handleSignal()" is deprecated, return "false" to keep the command running or "0" to exit successfully.', get_debug_type($command)); - // $exitCode = 0; - // } - - // if (false !== $exitCode) { - // exit($exitCode); - // } - // }); - // } - // } - - // 进入该方法 - if (null === $this->dispatcher) { - return $command->run($input, $output); - } - - // // bind before the console.command event, so the listeners have access to input options/arguments - // try { - // $command->mergeApplicationDefinition(); - // $input->bind($command->getDefinition()); - // } catch (ExceptionInterface) { - // // ignore invalid options/arguments for now, to allow the event listeners to customize the InputDefinition - // } - - // $event = new ConsoleCommandEvent($command, $input, $output); - // $e = null; - - // try { - // $this->dispatcher->dispatch($event, ConsoleEvents::COMMAND); - - // if ($event->commandShouldRun()) { - // $exitCode = $command->run($input, $output); - // } else { - // $exitCode = ConsoleCommandEvent::RETURN_CODE_DISABLED; - // } - // } catch (\Throwable $e) { - // $event = new ConsoleErrorEvent($input, $output, $e, $command); - // $this->dispatcher->dispatch($event, ConsoleEvents::ERROR); - // $e = $event->getError(); - - // if (0 === $exitCode = $event->getExitCode()) { - // $e = null; - // } - // } - - // $event = new ConsoleTerminateEvent($command, $input, $output, $exitCode); - // $this->dispatcher->dispatch($event, ConsoleEvents::TERMINATE); - - // if (null !== $e) { - // throw $e; - // } - - // return $event->getExitCode(); -} - -``` -查看`StartServer`中,没有`run`方法,查看父类`Command`, -> 文件位置: /vendor/symfony/console/Command/Command.php -```php - public function run(InputInterface $input, OutputInterface $output): int - { - // add the application arguments and options - $this->mergeApplicationDefinition(); - - // bind the input against the command specific arguments/options - try { - $input->bind($this->getDefinition()); - } catch (ExceptionInterface $e) { - if (!$this->ignoreValidationErrors) { - throw $e; - } - } - - $this->initialize($input, $output); - - // if (null !== $this->processTitle) { - // if (\function_exists('cli_set_process_title')) { - // if (!@cli_set_process_title($this->processTitle)) { - // if ('Darwin' === \PHP_OS) { - // $output->writeln('Running "cli_set_process_title" as an unprivileged user is not supported on MacOS.', OutputInterface::VERBOSITY_VERY_VERBOSE); - // } else { - // cli_set_process_title($this->processTitle); - // } - // } - // } elseif (\function_exists('setproctitle')) { - // setproctitle($this->processTitle); - // } elseif (OutputInterface::VERBOSITY_VERY_VERBOSE === $output->getVerbosity()) { - // $output->writeln('Install the proctitle PECL to be able to change the process title.'); - // } - // } - - // if ($input->isInteractive()) { - // $this->interact($input, $output); - // } - - // // The command name argument is often omitted when a command is executed directly with its run() method. - // // It would fail the validation if we didn't make sure the command argument is present, - // // since it's required by the application. - // if ($input->hasArgument('command') && null === $input->getArgument('command')) { - // $input->setArgument('command', $this->getName()); - // } - - $input->validate(); - - if ($this->code) { - $statusCode = ($this->code)($input, $output); - } else { - // 最终会执行execute方法 - $statusCode = $this->execute($input, $output); - - if (!\is_int($statusCode)) { - throw new \TypeError(sprintf('Return value of "%s::execute()" must be of the type int, "%s" returned.', static::class, get_debug_type($statusCode))); - } - } - - return is_numeric($statusCode) ? (int) $statusCode : 0; - } - -``` -分析上面代码可知,`run`方法最终会调用`execute`方法,继续往下看。 -#### StartServer类 -看了那么多代码,现在回到`StartServer`中的`execute`方法, > 文件位置:/vendor/hyperf/server/src/Command/StartServer.php + ```php namespace Hyperf\Server\Command; @@ -690,7 +366,7 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; //... -protected function execute(InputInterface $input, OutputInterface $output): int +protected function execute(InputInterface $input, OutputInterface $output): int // [!code focus:25] { // 检查环境 $this->checkEnvironment($output); @@ -708,101 +384,706 @@ protected function execute(InputInterface $input, OutputInterface $output): int } // 根据给定的参数配置服务 $serverFactory->configure($serverConfig); - + // 设置协程的hook标志,将所有相关的阻塞操作转换为非阻塞操作 Coroutine::set(['hook_flags' => swoole_hook_flags()]); - + // 启动服务 $serverFactory->start(); return 0; } ``` + > 默认情况下,配置文件中只有一个`http`服务。 -##### configure方法 -分析一下`configure`方法, +::: tip 总结 + +1. 获取`ServerFactory`实例,设置事件调度器,设置日志 +2. 获取`config`目录下`server.php`中的`server`配置,没有则抛异常 +3. 根据`config`创建对应的服务 +4. 设置协程的 `hook` 标志,将所有相关的阻塞操作转换为非阻塞操作 +5. 启动服务 +::: + +#### 配置服务 + +查看服务配置实现,调用`ServerFactory`类的`configure`方法, + ```php -public function configure(array $config): void +namespace Hyperf\Server; + +use Hyperf\Server\Entry\EventDispatcher; +use Hyperf\Server\Entry\Logger; +use Psr\Container\ContainerInterface; +use Psr\EventDispatcher\EventDispatcherInterface; +use Psr\Log\LoggerInterface; + +class ServerFactory { - // 创建一个serverConfig对象赋值给$this->config - $this->config = new ServerConfig($config); - // - $this->getServer()->init($this->config); + protected ?LoggerInterface $logger = null; + + protected ?EventDispatcherInterface $eventDispatcher = null; + + protected ?ServerInterface $server = null; + + protected ?ServerConfig $config = null; + + public function __construct(protected ContainerInterface $container) + { + } + + public function configure(array $config): void // [!code focus:8] + { + // 获取服务配置信息,包装成ServerConfig类 + $this->config = new ServerConfig($config); + + $this->getServer()->init($this->config); + } + + /** + * 启动Server + */ + public function start(): void + { + $this->getServer()->start(); + } + + /** + * 获取Server + */ + public function getServer(): ServerInterface // [!code focus:13] + { + if (! $this->server instanceof ServerInterface) { + $serverName = $this->config->getType(); + $this->server = new $serverName( + $this->container, + $this->getLogger(), + $this->getEventDispatcher() + ); + } + + return $this->server; + } + // 设置Server + public function setServer(Server $server): static + { + $this->server = $server; + return $this; + } + + /** + * 获取事件调度器 + */ + public function getEventDispatcher(): EventDispatcherInterface + { + if ($this->eventDispatcher instanceof EventDispatcherInterface) { + return $this->eventDispatcher; + } + return $this->getDefaultEventDispatcher(); + } + + /** + * 设置事件调度器 + */ + public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): static + { + $this->eventDispatcher = $eventDispatcher; + return $this; + } + + /** + * 获取Loger + */ + public function getLogger(): LoggerInterface + { + if ($this->logger instanceof LoggerInterface) { + return $this->logger; + } + return $this->getDefaultLogger(); + } + + public function setLogger(LoggerInterface $logger): static + { + $this->logger = $logger; + return $this; + } + + public function getConfig(): ?ServerConfig + { + return $this->config; + } + + private function getDefaultEventDispatcher(): EventDispatcherInterface + { + return new EventDispatcher(); + } + + private function getDefaultLogger(): LoggerInterface + { + return new Logger(); + } } -// 看一下ServerConfig类的构造函数 -public function __construct(protected array $config = []) -{ -// if (empty($config['servers'] ?? [])) { -// throw new InvalidArgumentException('Config server.servers not exist.'); -// } - -// $servers = []; -// foreach ($config['servers'] as $name => $item) { -// if (! isset($item['name']) && ! is_numeric($name)) { -// $item['name'] = $name; -// } -// $servers[] = Port::build($item); -// } - // 从这里可以看出,当配置文件中type不存在时,默认使用Hyperf\Server\Server类作为其类型 - $this->setType($config['type'] ?? Server::class) - ->setMode($config['mode'] ?? 0) - ->setServers($servers) - ->setProcesses($config['processes'] ?? []) - ->setSettings($config['settings'] ?? []) - ->setCallbacks($config['callbacks'] ?? []); -} - -public function start(): void - { - $this->getServer()->start(); - } - - public function getServer(): ServerInterface - { - // 首次进入,服务$this->server == null - if (! $this->server instanceof ServerInterface) { - // 这里默认的类型就为Hyperf\Server\Server类 - $serverName = $this->config->getType(); - // 实例化该Server类,传入日志和事件调度器 - $this->server = new $serverName( - $this->container, - $this->getLogger(), - $this->getEventDispatcher() - ); - } - - return $this->server; - } ``` -紧接着,执行`init`方法, + +1. 首先将配置文件中的`server`配置数组包装成`ServerConfig`类,传递给成员变量`config` +::: details 点我查看`ServerConfig` + +::: code-group +```php [ServerConfig] +namespace Hyperf\Server; + +use Hyperf\Contract\Arrayable; +use Hyperf\Server\Exception\InvalidArgumentException; + +/** + * @method ServerConfig setType(string $type) + * @method ServerConfig setMode(int $mode) + * @method ServerConfig setServers(array $servers) + * @method ServerConfig setProcesses(array $processes) + * @method ServerConfig setSettings(array $settings) + * @method ServerConfig setCallbacks(array $callbacks) + * @method string getType() + * @method int getMode() + * @method Port[] getServers() + * @method array getProcesses() + * @method array getSettings() + * @method array getCallbacks() + */ +class ServerConfig implements Arrayable +{ + public function __construct(protected array $config = []) // [!code focus:22] + { + if (empty($config['servers'] ?? [])) { + throw new InvalidArgumentException('Config server.servers not exist.'); + } + + $servers = []; + // 循环配置的server数组 + foreach ($config['servers'] as $name => $item) { + if (! isset($item['name']) && ! is_numeric($name)) { + $item['name'] = $name; + } + $servers[] = Port::build($item); + } + + $this->setType($config['type'] ?? Server::class) // 设置Server的类型,默认是Hyperf\Server\Server::class + ->setMode($config['mode'] ?? 0) + ->setServers($servers) + ->setProcesses($config['processes'] ?? []) + ->setSettings($config['settings'] ?? []) + ->setCallbacks($config['callbacks'] ?? []); + } + + public function __set($name, $value) + { + $this->set($name, $value); + } + + public function __get($name) + { + if (! $this->isAvailableProperty($name)) { + throw new \InvalidArgumentException(sprintf('Invalid property %s', $name)); + } + return $this->config[$name] ?? null; + } + + /** + * 魔术方法,当调用不存在的方法的时候,会调用__call方法 + */ + public function __call($name, $arguments) + { + $prefix = strtolower(substr($name, 0, 3)); + if (in_array($prefix, ['set', 'get'])) { + $propertyName = strtolower(substr($name, 3)); + if (! $this->isAvailableProperty($propertyName)) { + throw new \InvalidArgumentException(sprintf('Invalid property %s', $propertyName)); + } + return $prefix === 'set' ? $this->set($propertyName, ...$arguments) : $this->__get($propertyName); + } + + throw new \InvalidArgumentException(sprintf('Invalid method %s', $name)); + } + + public function addServer(Port $port): static + { + $this->config['servers'][] = $port; + return $this; + } + + public function toArray(): array + { + return $this->config; + } + + protected function set($name, $value): self + { + if (! $this->isAvailableProperty($name)) { + throw new \InvalidArgumentException(sprintf('Invalid property %s', $name)); + } + $this->config[$name] = $value; + return $this; + } + + private function isAvailableProperty(string $name) + { + return in_array($name, [ + 'type', 'mode', 'servers', 'processes', 'settings', 'callbacks', + ]); + } +} +``` +```php [Server build] +namespace Hyperf\Server; + +class Port +{ + protected string $name = 'http'; + + protected int $type = ServerInterface::SERVER_HTTP; + + protected string $host = '0.0.0.0'; + + protected int $port = 9501; + + protected int $sockType = 0; + + protected array $callbacks = []; + + protected array $settings = []; + + protected ?Option $options = null; + + public static function build(array $config): static + { + $config = self::filter($config); + + $port = new static(); + isset($config['name']) && $port->setName($config['name']); + isset($config['type']) && $port->setType($config['type']); + isset($config['host']) && $port->setHost($config['host']); + isset($config['port']) && $port->setPort($config['port']); + isset($config['sock_type']) && $port->setSockType($config['sock_type']); + isset($config['callbacks']) && $port->setCallbacks($config['callbacks']); + isset($config['settings']) && $port->setSettings($config['settings']); + isset($config['options']) && $port->setOptions(Option::make($config['options'])); + + return $port; + } + + public function getName(): string + { + return $this->name; + } + + public function setName(string $name): static + { + $this->name = $name; + return $this; + } + + public function getType(): int + { + return $this->type; + } + + public function setType(int $type): static + { + $this->type = $type; + return $this; + } + + public function getHost(): string + { + return $this->host; + } + + public function setHost(string $host): static + { + $this->host = $host; + return $this; + } + + public function getPort(): int + { + return $this->port; + } + + public function setPort(int $port): static + { + $this->port = $port; + return $this; + } + + public function getSockType(): int + { + return $this->sockType; + } + + public function setSockType(int $sockType): static + { + $this->sockType = $sockType; + return $this; + } + + public function getCallbacks(): array + { + return $this->callbacks; + } + + public function setCallbacks(array $callbacks): static + { + $this->callbacks = $callbacks; + return $this; + } + + public function getSettings(): array + { + return $this->settings; + } + + public function setSettings(array $settings): static + { + $this->settings = $settings; + return $this; + } + + public function getOptions(): ?Option + { + return $this->options; + } + + public function setOptions(Option $options): static + { + $this->options = $options; + return $this; + } + + private static function filter(array $config): array + { + if ((int) $config['type'] === ServerInterface::SERVER_BASE) { + $default = [ + 'open_http2_protocol' => false, + 'open_http_protocol' => false, + ]; + + $config['settings'] = array_merge($default, $config['settings'] ?? []); + } + + return $config; + } +} +``` +::: +2. 根据`Server`配置的服务类型,实例化对应服务,调用服务的`init`方法 +::: tip +(以默认配置为例,`server.php`文件中未配置`type`值,使用默认`Hyperf\Server\Server::class`) +::: + +3. `getServer`方法返回`Hyperf\Server\Server`实例 +4. 调用`Hyperf\Server\Server`类的`init`方法 + +#### 实例化Server + ```php // $this->getServer()返回的是一个Hyperf/Server/Server类实例 $this->getServer()->init($this->config); ``` -##### Hyperf\Server\Server类 > 文件位置:/vendor/hyperf/server/src/Server.php -查看其`init`方法 -```php + +::: code-group +```php [init方法] public function init(ServerConfig $config): ServerInterface { $this->initServers($config); return $this; } - // 代码很长,先分析第一行 - protected function initServers(ServerConfig $config) - { - $servers = $this->sortServers($config->getServers()); - //... - } - ``` +```php [initServers方法] +protected function initServers(ServerConfig $config) +{ + // 对server进行排序 + $servers = $this->sortServers($config->getServers()); + + foreach ($servers as $server) { + $name = $server->getName(); + $type = $server->getType(); + $host = $server->getHost(); + $port = $server->getPort(); + $sockType = $server->getSockType(); + $callbacks = $server->getCallbacks(); + + if (! $this->server instanceof SwooleServer) { + $this->server = $this->makeServer($type, $host, $port, $config->getMode(), $sockType); + $callbacks = array_replace($this->defaultCallbacks(), $config->getCallbacks(), $callbacks); + $this->registerSwooleEvents($this->server, $callbacks, $name); + $this->server->set(array_replace($config->getSettings(), $server->getSettings())); + ServerManager::add($name, [$type, current($this->server->ports)]); + + if (class_exists(BeforeMainServerStart::class)) { + // Trigger BeforeMainServerStart event, this event only trigger once before main server start. + $this->eventDispatcher->dispatch(new BeforeMainServerStart($this->server, $config->toArray())); + } + } else { + /** @var bool|SwoolePort $slaveServer */ + $slaveServer = $this->server->addlistener($host, $port, $sockType); + if (! $slaveServer) { + throw new \RuntimeException("Failed to listen server port [{$host}:{$port}]"); + } + $server->getSettings() && $slaveServer->set(array_replace($config->getSettings(), $server->getSettings())); + $this->registerSwooleEvents($slaveServer, $callbacks, $name); + ServerManager::add($name, [$type, $slaveServer]); + } + + // Trigger beforeStart event. + if (isset($callbacks[Event::ON_BEFORE_START])) { + [$class, $method] = $callbacks[Event::ON_BEFORE_START]; + if ($this->container->has($class)) { + $this->container->get($class)->{$method}(); + } + } + + if (class_exists(BeforeServerStart::class)) { + // Trigger BeforeServerStart event. + $this->eventDispatcher->dispatch(new BeforeServerStart($name)); + } + } +} +``` +```php [sortServer方法] +protected function sortServers(array $servers): array +{ + $sortServers = []; + foreach ($servers as $server) { + switch ($server->getType()) { + case ServerInterface::SERVER_HTTP: + $this->enableHttpServer = true; + if (! $this->enableWebsocketServer) { + array_unshift($sortServers, $server); + } else { + $sortServers[] = $server; + } + break; + case ServerInterface::SERVER_WEBSOCKET: + $this->enableWebsocketServer = true; + array_unshift($sortServers, $server); + break; + default: + $sortServers[] = $server; + break; + } + } + + return $sortServers; +} +``` +```php [Server类源码] +namespace Hyperf\Server; + +use Hyperf\Contract\MiddlewareInitializerInterface; +use Hyperf\Framework\Bootstrap; +use Hyperf\Framework\Event\BeforeMainServerStart; +use Hyperf\Framework\Event\BeforeServerStart; +use Hyperf\Server\Exception\RuntimeException; +use Psr\Container\ContainerInterface; +use Psr\EventDispatcher\EventDispatcherInterface; +use Psr\Log\LoggerInterface; +use Swoole\Http\Server as SwooleHttpServer; +use Swoole\Server as SwooleServer; +use Swoole\Server\Port as SwoolePort; +use Swoole\WebSocket\Server as SwooleWebSocketServer; + +class Server implements ServerInterface +{ + protected bool $enableHttpServer = false; + + protected bool $enableWebsocketServer = false; + + protected ?SwooleServer $server = null; + + protected array $onRequestCallbacks = []; + + public function __construct(protected ContainerInterface $container, protected LoggerInterface $logger, protected EventDispatcherInterface $eventDispatcher) + { + } + + public function init(ServerConfig $config): ServerInterface // [!code ] + { + $this->initServers($config); + + return $this; + } + + public function start(): void + { + $this->server->start(); + } + + public function getServer(): SwooleServer + { + return $this->server; + } + + protected function initServers(ServerConfig $config) + { + $servers = $this->sortServers($config->getServers()); + + foreach ($servers as $server) { + $name = $server->getName(); + $type = $server->getType(); + $host = $server->getHost(); + $port = $server->getPort(); + $sockType = $server->getSockType(); + $callbacks = $server->getCallbacks(); + + if (! $this->server instanceof SwooleServer) { + $this->server = $this->makeServer($type, $host, $port, $config->getMode(), $sockType); + $callbacks = array_replace($this->defaultCallbacks(), $config->getCallbacks(), $callbacks); + $this->registerSwooleEvents($this->server, $callbacks, $name); + $this->server->set(array_replace($config->getSettings(), $server->getSettings())); + ServerManager::add($name, [$type, current($this->server->ports)]); + + if (class_exists(BeforeMainServerStart::class)) { + // Trigger BeforeMainServerStart event, this event only trigger once before main server start. + $this->eventDispatcher->dispatch(new BeforeMainServerStart($this->server, $config->toArray())); + } + } else { + /** @var bool|SwoolePort $slaveServer */ + $slaveServer = $this->server->addlistener($host, $port, $sockType); + if (! $slaveServer) { + throw new \RuntimeException("Failed to listen server port [{$host}:{$port}]"); + } + $server->getSettings() && $slaveServer->set(array_replace($config->getSettings(), $server->getSettings())); + $this->registerSwooleEvents($slaveServer, $callbacks, $name); + ServerManager::add($name, [$type, $slaveServer]); + } + + // Trigger beforeStart event. + if (isset($callbacks[Event::ON_BEFORE_START])) { + [$class, $method] = $callbacks[Event::ON_BEFORE_START]; + if ($this->container->has($class)) { + $this->container->get($class)->{$method}(); + } + } + + if (class_exists(BeforeServerStart::class)) { + // Trigger BeforeServerStart event. + $this->eventDispatcher->dispatch(new BeforeServerStart($name)); + } + } + } + + /** + * @param Port[] $servers + * @return Port[] + */ + protected function sortServers(array $servers): array + { + $sortServers = []; + foreach ($servers as $server) { + switch ($server->getType()) { + case ServerInterface::SERVER_HTTP: + $this->enableHttpServer = true; + if (! $this->enableWebsocketServer) { + array_unshift($sortServers, $server); + } else { + $sortServers[] = $server; + } + break; + case ServerInterface::SERVER_WEBSOCKET: + $this->enableWebsocketServer = true; + array_unshift($sortServers, $server); + break; + default: + $sortServers[] = $server; + break; + } + } + + return $sortServers; + } + + protected function makeServer(int $type, string $host, int $port, int $mode, int $sockType): SwooleServer + { + switch ($type) { + case ServerInterface::SERVER_HTTP: + return new SwooleHttpServer($host, $port, $mode, $sockType); + case ServerInterface::SERVER_WEBSOCKET: + return new SwooleWebSocketServer($host, $port, $mode, $sockType); + case ServerInterface::SERVER_BASE: + return new SwooleServer($host, $port, $mode, $sockType); + } + + throw new RuntimeException('Server type is invalid.'); + } + + protected function registerSwooleEvents(SwoolePort|SwooleServer $server, array $events, string $serverName): void + { + foreach ($events as $event => $callback) { + if (! Event::isSwooleEvent($event)) { + continue; + } + if (is_array($callback)) { + [$className, $method] = $callback; + if (array_key_exists($className . $method, $this->onRequestCallbacks)) { + $this->logger->warning(sprintf('%s will be replaced by %s. Each server should have its own onRequest callback. Please check your configs.', $this->onRequestCallbacks[$className . $method], $serverName)); + } + + $this->onRequestCallbacks[$className . $method] = $serverName; + $class = $this->container->get($className); + if (method_exists($class, 'setServerName')) { + // Override the server name. + $class->setServerName($serverName); + } + if ($class instanceof MiddlewareInitializerInterface) { + $class->initCoreMiddleware($serverName); + } + $callback = [$class, $method]; + } + $server->on($event, $callback); + } + } + + protected function defaultCallbacks() + { + $hasCallback = class_exists(Bootstrap\StartCallback::class) + && class_exists(Bootstrap\ManagerStartCallback::class) + && class_exists(Bootstrap\WorkerStartCallback::class); + + if ($hasCallback) { + $callbacks = [ + Event::ON_MANAGER_START => [Bootstrap\ManagerStartCallback::class, 'onManagerStart'], + Event::ON_WORKER_START => [Bootstrap\WorkerStartCallback::class, 'onWorkerStart'], + Event::ON_WORKER_STOP => [Bootstrap\WorkerStopCallback::class, 'onWorkerStop'], + Event::ON_WORKER_EXIT => [Bootstrap\WorkerExitCallback::class, 'onWorkerExit'], + ]; + if ($this->server->mode === SWOOLE_BASE) { + return $callbacks; + } + + return array_merge([ + Event::ON_START => [Bootstrap\StartCallback::class, 'onStart'], + ], $callbacks); + } + + return [ + Event::ON_WORKER_START => function (SwooleServer $server, int $workerId) { + printf('Worker %d started.' . PHP_EOL, $workerId); + }, + ]; + } +} +``` +::: 分析`sortServicers`方法, + ```php // $config->getServers()返回的是一个Hyperf\Server\Port类对象数组 $servers = $this->sortServers($config->getServers()); ``` -这里返回排好序的Port类对象数组。 + +这里返回排好序的`Port`类对象数组。 之后循环遍历`$servers`数组,每个`value`是一个`Port`对象,走`if` + ```php foreach ($servers as $server) { $name = $server->getName(); @@ -854,9 +1135,12 @@ foreach ($servers as $server) { } } ``` + ###### makeServer方法 + 分析可知,`makeServer`方法的作用就是根据`$type`参数,返回对应的`Server`类型。 根据配置文件中的`type`参数可知,返回的是一个`Swoole\Server`对象实例。 + ```php use Swoole\Server as SwooleServer; @@ -876,7 +1160,9 @@ protected function makeServer(int $type, string $host, int $port, int $mode, int ``` ###### registerSwooleEvents方法 + 看这一行, + ```php $this->registerSwooleEvents($this->server, $callbacks, $name); @@ -915,13 +1201,16 @@ protected function registerSwooleEvents(SwoolePort|SwooleServer $server, array $ } } ``` + 这是所有的回调事件,如下图 -![](image https://oss.xiaokeaii.top/2024/event.png ) +![](https://oss.xiaokeaii.top/2024/event.png) 查看以下代码,这里进行配置和初始化中间件相关的内容 + ```php $class->initCoreMiddleware($serverName); ``` + > 文件位置:/vendor/hyperf/http-server/src/Server.php ```php @@ -941,7 +1230,9 @@ public function initCoreMiddleware(string $serverName): void $this->initOption(); } ``` + 逐行分析, + ```php protected function createCoreMiddleware(): CoreMiddlewareInterface { @@ -978,8 +1269,11 @@ protected function initOption(): void $this->option->setMustSortMiddlewaresByMiddlewares($this->middlewares); } ``` + ###### ServerManager::add方法 + 上面分析完了,继续看下面的代码。 + ```php // 设置sever运行时的参数 $this->server->set(array_replace($config->getSettings(), $server->getSettings())); @@ -991,7 +1285,6 @@ ServerManager::add($name, [$type, current($this->server->ports)]); `Hyperf\Server\ServerFactory`工厂类`configure`方法完成。 回到`StartServer`类的`execute`方法。 - 该方法的`$serverFactory->configure($serverConfig);`这一行执行完成。 ```php @@ -1000,8 +1293,10 @@ Coroutine::set(['hook_flags' => swoole_hook_flags()]); ``` ##### start方法 + 终于可以启动服务了, > 文件位置:/vendor/hyperf/server/src/ServerFactory.php + ```php $serverFactory->start(); public function start(): void @@ -1009,13 +1304,16 @@ public function start(): void $this->getServer()->start(); } ``` + `$this->getServer()`前面已经生成,这里直接返回`Hyperf\Server\Server`实例。 > 文件位置:/vendor/hyperf/server/src/Server.php + ```php public function start(): void { $this->server->start(); } ``` + 其中`$this->server`对应的是`Swoole\Server`实例。调用`start`方法,实际就是启动`Swoole`的服务。 -此时`server`启动,等待请求。 \ No newline at end of file +此时`server`启动,等待请求。 diff --git a/src/hyperf/附录1 container的get方法.md b/src/hyperf/附录1 container的get方法.md new file mode 100644 index 0000000..f7e2251 --- /dev/null +++ b/src/hyperf/附录1 container的get方法.md @@ -0,0 +1,191 @@ +--- +title: 附录1 container的get方法 +--- + +分析一下`get`方法源码, + +```php +public function get($id) +{ + // 如果解析过,则直接返回 + if (isset($this->resolvedEntries[$id]) || array_key_exists($id, $this->resolvedEntries)) { + return $this->resolvedEntries[$id]; + } + return $this->resolvedEntries[$id] = $this->make($id); +} +``` + +上面可以看出,首次解析不走`if`,查看`make`方法, + +```php +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); +} +``` + +前面绑定阶段,已经将`ApplicationInterface`绑定到`Hyperf\Framework\ApplicationFactory`类上,通过`DefinitionSource`类,将对应实现转换成对应`DefinitionInterface`接口的实现类。 + +```php +// 调用逻辑 +public function __construct(array $source) +{ + $this->source = $this->normalizeSource($source); +} + +protected function normalizeSource(array $source): array +{ + $definitions = []; + foreach ($source as $identifier => $definition) { + $normalizedDefinition = $this->normalizeDefinition($identifier, $definition); + if (! is_null($normalizedDefinition)) { + $definitions[$identifier] = $normalizedDefinition; + } + } + return $definitions; +} + +protected function normalizeDefinition(string $identifier, $definition): ?DefinitionInterface +{ + if ($definition instanceof PriorityDefinition) { + $definition = $definition->getDefinition(); + } + + + if (is_string($definition) && class_exists($definition)) { + if (method_exists($definition, '__invoke')) { + return new FactoryDefinition($identifier, $definition, []); + } + return $this->autowire($identifier, new ObjectDefinition($identifier, $definition)); + } + + if (is_callable($definition)) { + return new FactoryDefinition($identifier, $definition, []); + } + + return null; +} +``` + +查看`ApplicationFactory`类,存在`__invoke`方法,返回的是一个`FactoryDefinition`对象。 +![](image ) + +```php +$definition = $this->getDefinition($name); +``` + +所以这行返回的是一个`FactoryDefinition`对象。打印结果如下, +![](image ) + +#### resolveDefinition方法 + +继续分析,查看`resolveDefinition`方法, +> 文件位置: /vendor/hyperf/di/src/Container.php + +```php +protected function resolveDefinition(DefinitionInterface $definition, array $parameters = []) +{ + return $this->definitionResolver->resolve($definition, $parameters); +} +``` + +上面可以看到,这里调用了解析调度器`ResolverDispatcher`的`resolve`方法。 + +##### resolve方法 +> +> 文件位置:/vendor/hyperf/di/src/Resolver/ResolverDispatcher.php + +```php +public function resolve(DefinitionInterface $definition, array $parameters = []) +{ + if ($definition instanceof SelfResolvingDefinitionInterface) { + + return $definition->resolve($this->container); + } + $guard = DepthGuard::getInstance(); + + return $guard->call( + $definition->getName(), + fn () => $this->getDefinitionResolver($definition)->resolve($definition, $parameters) + ); +} +``` + +这里`FactoryDefinition`不是`SelfResolvingDefinitionInterface`接口的实现类,所以不走`if`。 +`$guard`是`DepthGuard`类的实例,调用`call`方法, + +###### call方法 +> +> 文件位置:/vendor/hyperf/di/src/Resolver/DepthGuard.php + +```php +public function call(string $name, callable $callable) +{ + try { + $this->increment(); + return $callable(); + } catch (CircularDependencyException $exception) { + $exception->addDefinitionName($name); + throw $exception; + } finally { + $this->decrement(); + } +} +``` + +这里最终执行传入的闭包,回到上面,执行闭包。 +查看闭包执行源码, +> ./vendor/hyperf/di/src/Resolver/ResolverDispatcher.php +> +##### getDefinitionResolver方法 + +```php +private function getDefinitionResolver(DefinitionInterface $definition): ResolverInterface +{ + return match (true) { + $definition instanceof ObjectDefinition => $this->objectResolver ??= new ObjectResolver($this->container, $this), + $definition instanceof FactoryDefinition => $this->factoryResolver ??= new FactoryResolver($this->container, $this), + default => throw new RuntimeException('No definition resolver was configured for definition of type ' . get_class($definition)), + }; +} +``` + +从代码可知,这里会根据`DefinitionInterface`的实现类,将对应解析器赋值给成员变量。 +这里传进来的是`FactoryDefinition`类,所以会执行第二条。 +紧接着调用`FactoryResolver`类的`resolve`方法。 + +###### resolve方法 +> +> 文件位置:/vendor/hyperf/di/src/Resolver/FactoryResolver.php + +```php +public function resolve(DefinitionInterface $definition, array $parameters = []) +{ + $callable = null; + try { + $callable = $definition->getFactory(); + if (! method_exists($callable, '__invoke')) { + throw new NotCallableException(); + } + if (is_string($callable)) { + $callable = $this->container->get($callable); + } + return $callable($this->container, $parameters); + } catch (NotCallableException $e) { + // Custom error message to help debugging + if (is_string($callable) && class_exists($callable) && method_exists($callable, '__invoke')) { + throw new InvalidDefinitionException(sprintf('Entry "%s" cannot be resolved: factory %s. Invokable classes cannot be automatically resolved if autowiring is disabled on the container, you need to enable autowiring or define the entry manually.', $definition->getName(), $e->getMessage())); + } + + throw new InvalidDefinitionException(sprintf('Entry "%s" cannot be resolved: factory %s', $definition->getName(), $e->getMessage())); + } +} +``` + +分析该方法,`$callable`是工厂的类名,如果传递过来的是字符串,则通过容器解析出对应的类,方法同理,不再赘述。 +最终调用该工厂类的`__invoke`方法返回解析结果。 diff --git a/src/hyperf/附录2 注解命令获取.md b/src/hyperf/附录2 注解命令获取.md new file mode 100644 index 0000000..126e295 --- /dev/null +++ b/src/hyperf/附录2 注解命令获取.md @@ -0,0 +1,3 @@ +--- +title: 附录2 注解命令获取 +--- \ No newline at end of file diff --git a/src/hyperf/附录3 命令注册Application.md b/src/hyperf/附录3 命令注册Application.md new file mode 100644 index 0000000..b70c30d --- /dev/null +++ b/src/hyperf/附录3 命令注册Application.md @@ -0,0 +1,65 @@ +--- +title: 附录3 命令注册Application +--- + +### 注册命令 + +查看`Application`类的`add`方法, + +```php +public function add(Command $command) +{ + $this->init(); + + $command->setApplication($this); + + if (!$command->isEnabled()) { + $command->setApplication(null); + + return null; + } + + if (!$command instanceof LazyCommand) { + // Will throw if the command is not correctly initialized. + $command->getDefinition(); + } + + if (!$command->getName()) { + throw new LogicException(sprintf('The command defined in "%s" cannot have an empty name.', get_debug_type($command))); + } + // 重点看这行,将命令的名称以及对应实现添加到命令属性中 + $this->commands[$command->getName()] = $command; + + foreach ($command->getAliases() as $alias) { + $this->commands[$alias] = $command; + } + return $command; +} +``` + +打印配置的命令数组,打开第一个`/vendor/hyperf/server/src/Command/StartServer.php`命令类, +![](image ) +> 我们可以发现,在父类的构造方法中,会将`name`赋值给该命令类的成员变量,之后通过`getName`方法获取命令的名称 + +```php +public function __construct(private ContainerInterface $container) +{ + parent::__construct('start'); + $this->setDescription('Start hyperf servers.'); +} +// StartServer的父类的构造方法 +public function __construct(?string $name = null) +{ + $this->definition = new InputDefinition(); + //... + if (null !== $name) { + $this->setName($name); + } + //... +} + +public function getName(): ?string +{ + return $this->name; +} +``` diff --git a/src/hyperf/附录4 Application_Run.md b/src/hyperf/附录4 Application_Run.md new file mode 100644 index 0000000..caf640b --- /dev/null +++ b/src/hyperf/附录4 Application_Run.md @@ -0,0 +1,408 @@ +--- +title: 附录4 Application_Run +--- +```php +(function () { + Hyperf\Di\ClassLoader::init(); + /** @var Psr\Container\ContainerInterface $container */ + $container = require BASE_PATH . '/config/container.php'; + + $application = $container->get(Hyperf\Contract\ApplicationInterface::class); + $application->run(); // [!code focus] +})(); +``` + +从上面分析可知,`$application`是一个`Symfony\Component\Console\Application`对象,调用`run`方法。 +先看源码, + +```php +public function run(?InputInterface $input = null, ?OutputInterface $output = null): int +{ + // 设置终端的高度和宽度 + if (\function_exists('putenv')) { + @putenv('LINES='.$this->terminal->getHeight()); + @putenv('COLUMNS='.$this->terminal->getWidth()); + } + // 初始化输入和输出对象,如果没有提供输入和输出对象,则创建默认 + $input ??= new ArgvInput(); + $output ??= new ConsoleOutput(); + // 渲染异常的闭包函数 + $renderException = function (\Throwable $e) use ($output) { + if ($output instanceof ConsoleOutputInterface) { + $this->renderThrowable($e, $output->getErrorOutput()); + } else { + $this->renderThrowable($e, $output); + } + }; + if ($phpHandler = set_exception_handler($renderException)) { + restore_exception_handler(); + if (!\is_array($phpHandler) || !$phpHandler[0] instanceof ErrorHandler) { + $errorHandler = true; + } elseif ($errorHandler = $phpHandler[0]->setExceptionHandler($renderException)) { + $phpHandler[0]->setExceptionHandler($errorHandler); + } + } + // 根据输入参数和环境变量配置输入和输出对象 + $this->configureIO($input, $output); + + try { + $exitCode = $this->doRun($input, $output); + } catch (\Throwable $e) { + if ($e instanceof \Exception && !$this->catchExceptions) { + throw $e; + } + if (!$e instanceof \Exception && !$this->catchErrors) { + throw $e; + } + + $renderException($e); + + $exitCode = $e->getCode(); + if (is_numeric($exitCode)) { + $exitCode = (int) $exitCode; + if ($exitCode <= 0) { + $exitCode = 1; + } + } else { + $exitCode = 1; + } + } finally { + // if the exception handler changed, keep it + // otherwise, unregister $renderException + if (!$phpHandler) { + if (set_exception_handler($renderException) === $renderException) { + restore_exception_handler(); + } + restore_exception_handler(); + } elseif (!$errorHandler) { + $finalHandler = $phpHandler[0]->setExceptionHandler(null); + if ($finalHandler !== $renderException) { + $phpHandler[0]->setExceptionHandler($finalHandler); + } + } + } + + if ($this->autoExit) { + if ($exitCode > 255) { + $exitCode = 255; + } + + exit($exitCode); + } + + return $exitCode; +} +``` + +#### doRun方法 +> +> 文件位置:/vendor/symfony/console/Application.php + +```php +public function doRun(InputInterface $input, OutputInterface $output) +{ + // 输出版本信息 + if (true === $input->hasParameterOption(['--version', '-V'], true)) { + $output->writeln($this->getLongVersion()); + + return 0; + } + + try { + // Makes ArgvInput::getFirstArgument() able to distinguish an option from an argument. + $input->bind($this->getDefinition()); + } catch (ExceptionInterface) { + // Errors must be ignored, full binding/validation happens later when the command is known. + } + + // 获取命令名称,项目启动代码是 php bin/hyperf.php start, + // 所以命令名称就是 start + $name = $this->getCommandName($input); + // 如果命令名称是--help,则显示帮助信息 + if (true === $input->hasParameterOption(['--help', '-h'], true)) { + if (!$name) { + $name = 'help'; + $input = new ArrayInput(['command_name' => $this->defaultCommand]); + } else { + $this->wantHelps = true; + } + } + // 如果命令名称不存在,则显示默认信息 + if (!$name) { + $name = $this->defaultCommand; + $definition = $this->getDefinition(); + $definition->setArguments(array_merge( + $definition->getArguments(), + [ + 'command' => new InputArgument('command', InputArgument::OPTIONAL, $definition->getArgument('command')->getDescription(), $name), + ] + )); + } + + try { + $this->runningCommand = null; + // 从应用中找到对应的命令 + $command = $this->find($name); + } catch (\Throwable $e) { + // 找不到的时候的异常处理 + if (($e instanceof CommandNotFoundException && !$e instanceof NamespaceNotFoundException) && 1 === \count($alternatives = $e->getAlternatives()) && $input->isInteractive()) { + $alternative = $alternatives[0]; + + $style = new SymfonyStyle($input, $output); + $output->writeln(''); + $formattedBlock = (new FormatterHelper())->formatBlock(sprintf('Command "%s" is not defined.', $name), 'error', true); + $output->writeln($formattedBlock); + if (!$style->confirm(sprintf('Do you want to run "%s" instead? ', $alternative), false)) { + if (null !== $this->dispatcher) { + $event = new ConsoleErrorEvent($input, $output, $e); + $this->dispatcher->dispatch($event, ConsoleEvents::ERROR); + + return $event->getExitCode(); + } + + return 1; + } + + $command = $this->find($alternative); + } else { + if (null !== $this->dispatcher) { + $event = new ConsoleErrorEvent($input, $output, $e); + $this->dispatcher->dispatch($event, ConsoleEvents::ERROR); + + if (0 === $event->getExitCode()) { + return 0; + } + + $e = $event->getError(); + } + + try { + if ($e instanceof CommandNotFoundException && $namespace = $this->findNamespace($name)) { + $helper = new DescriptorHelper(); + $helper->describe($output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output, $this, [ + 'format' => 'txt', + 'raw_text' => false, + 'namespace' => $namespace, + 'short' => false, + ]); + + return isset($event) ? $event->getExitCode() : 1; + } + + throw $e; + } catch (NamespaceNotFoundException) { + throw $e; + } + } + } + // 如果commend继承LazyCommand类,则属于懒加载命令 + // 延迟实例化命令对象,以提高应用程序启动速度 + if ($command instanceof LazyCommand) { + $command = $command->getCommand(); + } + // 将运行的命令赋值给runningCommand属性 + $this->runningCommand = $command; + // 执行命令 + $exitCode = $this->doRunCommand($command, $input, $output); + $this->runningCommand = null; + + return $exitCode; +} +``` + +##### doRunCommand方法 + +这里正式进行命令的执行操作; 我们的启动命令是`start`,我们就以`start`命令为例,该命令对应的类是`StartServer`。 + +> 文件位置:/vendor/symfony/console/Application.php + +```php +protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output) +{ + // 循环命令的帮助器集合,初始化时未配置,默认是有4个 + // return new HelperSet([ + // new FormatterHelper(), + // new DebugFormatterHelper(), + // new ProcessHelper(), + // new QuestionHelper(), + // ]); + foreach ($command->getHelperSet() as $helper) { + if ($helper instanceof InputAwareInterface) { + $helper->setInput($input); + } + } + // 获取当前命令是否实现了`SignalableCommandInterface`接口,如果是,获取订阅的信号列表 + // $commandSignals = $command instanceof SignalableCommandInterface ? $command->getSubscribedSignals() : []; + // 检查是否存在需要处理的信号或者事件,$commandSignals初始化时为空,不处理信号 + // if ($commandSignals || $this->dispatcher && $this->signalsToDispatchEvent) { + // if (!$this->signalRegistry) { + // throw new RuntimeException('Unable to subscribe to signal events. Make sure that the "pcntl" extension is installed and that "pcntl_*" functions are not disabled by your php.ini\'s "disable_functions" directive.'); + // } + + // if (Terminal::hasSttyAvailable()) { + // $sttyMode = shell_exec('stty -g'); + + // foreach ([\SIGINT, \SIGTERM] as $signal) { + // $this->signalRegistry->register($signal, static fn () => shell_exec('stty '.$sttyMode)); + // } + // } + + // if ($this->dispatcher) { + // // We register application signals, so that we can dispatch the event + // foreach ($this->signalsToDispatchEvent as $signal) { + // $event = new ConsoleSignalEvent($command, $input, $output, $signal); + + // $this->signalRegistry->register($signal, function ($signal) use ($event, $command, $commandSignals) { + // $this->dispatcher->dispatch($event, ConsoleEvents::SIGNAL); + // $exitCode = $event->getExitCode(); + + // // If the command is signalable, we call the handleSignal() method + // if (\in_array($signal, $commandSignals, true)) { + // $exitCode = $command->handleSignal($signal, $exitCode); + // // BC layer for Symfony <= 5 + // if (null === $exitCode) { + // trigger_deprecation('symfony/console', '6.3', 'Not returning an exit code from "%s::handleSignal()" is deprecated, return "false" to keep the command running or "0" to exit successfully.', get_debug_type($command)); + // $exitCode = 0; + // } + // } + + // if (false !== $exitCode) { + // $event = new ConsoleTerminateEvent($command, $event->getInput(), $event->getOutput(), $exitCode, $signal); + // $this->dispatcher->dispatch($event, ConsoleEvents::TERMINATE); + + // exit($event->getExitCode()); + // } + // }); + // } + + // // then we register command signals, but not if already handled after the dispatcher + // $commandSignals = array_diff($commandSignals, $this->signalsToDispatchEvent); + // } + + // foreach ($commandSignals as $signal) { + // $this->signalRegistry->register($signal, function (int $signal) use ($command): void { + // $exitCode = $command->handleSignal($signal); + // // BC layer for Symfony <= 5 + // if (null === $exitCode) { + // trigger_deprecation('symfony/console', '6.3', 'Not returning an exit code from "%s::handleSignal()" is deprecated, return "false" to keep the command running or "0" to exit successfully.', get_debug_type($command)); + // $exitCode = 0; + // } + + // if (false !== $exitCode) { + // exit($exitCode); + // } + // }); + // } + // } + + // 进入该方法 + if (null === $this->dispatcher) { + return $command->run($input, $output); + } + + // // bind before the console.command event, so the listeners have access to input options/arguments + // try { + // $command->mergeApplicationDefinition(); + // $input->bind($command->getDefinition()); + // } catch (ExceptionInterface) { + // // ignore invalid options/arguments for now, to allow the event listeners to customize the InputDefinition + // } + + // $event = new ConsoleCommandEvent($command, $input, $output); + // $e = null; + + // try { + // $this->dispatcher->dispatch($event, ConsoleEvents::COMMAND); + + // if ($event->commandShouldRun()) { + // $exitCode = $command->run($input, $output); + // } else { + // $exitCode = ConsoleCommandEvent::RETURN_CODE_DISABLED; + // } + // } catch (\Throwable $e) { + // $event = new ConsoleErrorEvent($input, $output, $e, $command); + // $this->dispatcher->dispatch($event, ConsoleEvents::ERROR); + // $e = $event->getError(); + + // if (0 === $exitCode = $event->getExitCode()) { + // $e = null; + // } + // } + + // $event = new ConsoleTerminateEvent($command, $input, $output, $exitCode); + // $this->dispatcher->dispatch($event, ConsoleEvents::TERMINATE); + + // if (null !== $e) { + // throw $e; + // } + + // return $event->getExitCode(); +} + +``` +查看`StartServer`中,没有`run`方法,查看父类`Command`, +> 文件位置: /vendor/symfony/console/Command/Command.php + +```php + public function run(InputInterface $input, OutputInterface $output): int + { + // add the application arguments and options + $this->mergeApplicationDefinition(); + + // bind the input against the command specific arguments/options + try { + $input->bind($this->getDefinition()); + } catch (ExceptionInterface $e) { + if (!$this->ignoreValidationErrors) { + throw $e; + } + } + + $this->initialize($input, $output); + + // if (null !== $this->processTitle) { + // if (\function_exists('cli_set_process_title')) { + // if (!@cli_set_process_title($this->processTitle)) { + // if ('Darwin' === \PHP_OS) { + // $output->writeln('Running "cli_set_process_title" as an unprivileged user is not supported on MacOS.', OutputInterface::VERBOSITY_VERY_VERBOSE); + // } else { + // cli_set_process_title($this->processTitle); + // } + // } + // } elseif (\function_exists('setproctitle')) { + // setproctitle($this->processTitle); + // } elseif (OutputInterface::VERBOSITY_VERY_VERBOSE === $output->getVerbosity()) { + // $output->writeln('Install the proctitle PECL to be able to change the process title.'); + // } + // } + + // if ($input->isInteractive()) { + // $this->interact($input, $output); + // } + + // // The command name argument is often omitted when a command is executed directly with its run() method. + // // It would fail the validation if we didn't make sure the command argument is present, + // // since it's required by the application. + // if ($input->hasArgument('command') && null === $input->getArgument('command')) { + // $input->setArgument('command', $this->getName()); + // } + + $input->validate(); + + if ($this->code) { + $statusCode = ($this->code)($input, $output); + } else { + // 最终会执行execute方法 + $statusCode = $this->execute($input, $output); + + if (!\is_int($statusCode)) { + throw new \TypeError(sprintf('Return value of "%s::execute()" must be of the type int, "%s" returned.', static::class, get_debug_type($statusCode))); + } + } + + return is_numeric($statusCode) ? (int) $statusCode : 0; + } + +``` + +分析上面代码可知,`run`方法最终会调用`execute`方法,继续往下看。