Skip to content
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(); 
})();

从上面分析可知,$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('<comment>Running "cli_set_process_title" as an unprivileged user is not supported on MacOS.</comment>', 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('<comment>Install the proctitle PECL to be able to change the process title.</comment>');
    //      }
    //  }

    //  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方法,继续往下看。