最近有一个需求,统计所有的api,生成文档页面,然后浏览器访问该页面,可以查看所有的接口请求方式,入参与返回参数,并且可以在线测试。这时候想起来了fastadmin有一个根据命令行执行生成文档的操作。

php think api执行的文件是application/admin/command/Api.php中的文件。下面的操作大部分都是查找如何调用的,但是没找到如何实现调用的。

我们来看看:
常用命令:

  1. //一键生成API文档
  2. php think api --force=true
  3. //指定https://www.example.com为API接口请求域名,默认为空
  4. php think api -u https://www.example.com --force=true
  5. //输出自定义文件为myapi.html,默认为api.html
  6. php think api -o myapi.html --force=true
  7. //修改API模板为mytemplate.html,默认为index.html
  8. php think api -e mytemplate.html --force=true
  9. //修改标题为FastAdmin,作者为作者
  10. php think api -t FastAdmin -a Karson --force=true
  11. //查看API接口命令行帮助
  12. php think api -h

参数参考:

  1. -u, --url[=URL] 默认API请求URL地址 [default: ""]
  2. -m, --module[=MODULE] 模块名(admin/index/api) [default: "api"]
  3. -o, --output[=OUTPUT] 输出文件 [default: "api.html"]
  4. -e, --template[=TEMPLATE] 模板文件 [default: "index.html"]
  5. -f, --force[=FORCE] 覆盖模式 [default: false]
  6. -t, --title[=TITLE] 文档标题 [default: "FastAdmin"]
  7. -a, --author[=AUTHOR] 文档作者 [default: "FastAdmin"]
  8. -c, --class[=CLASS] 扩展类 (multiple values allowed)
  9. -l, --language[=LANGUAGE] 语言 [default: "zh-cn"]

我们执行命令行看看

  1. php think api
  2. Build Successed!

这个时候我们可以看到 public目录下 多出了个 api.html页面,我们访问的时候,可以看到生成了一个文档的页面。

那么我们来看看这个命令是如何实现的

  1. php think api
  1. 首先,php文件执行了think文件,我们打开think文件看看 ```php // 定义项目路径 define(‘APPPATH’, _DIR . ‘/application/‘);

// 加载框架引导文件 require ‘./thinkphp/console.php’;

  1. 2. 这里引入了console.php文件,我们进入查看一下
  2. ```php
  3. namespace think;
  4. // ThinkPHP 引导文件
  5. // 加载基础文件
  6. require __DIR__ . '/base.php';
  7. // 执行应用
  8. App::initCommon();
  9. Console::init();
  1. 这里初始化了APP类和Console类。

    1. 查看App::initCommon()方法,如下,返回了配置信息 ```php /**
    • 初始化应用,并返回配置信息
    • @access public
    • @return array */ public static function initCommon() { if (empty(self::$init)) { if (defined(‘APP_NAMESPACE’)) {

      1. self::$namespace = APP_NAMESPACE;

      }

      Loader::addNamespace(self::$namespace, APP_PATH);

      // 初始化应用 $config = self::init(); self::$suffix = $config[‘class_suffix’];

      // 应用调试模式 self::$debug = Env::get(‘app_debug’, Config::get(‘app_debug’));

      if (!self::$debug) {

      1. ini_set('display_errors', 'Off');

      } elseif (!IS_CLI) {

      1. // 重新申请一块比较大的 buffer
      2. if (ob_get_level() > 0) {
      3. $output = ob_get_clean();
      4. }
      5. ob_start();
      6. if (!empty($output)) {
      7. echo $output;
      8. }

      }

      if (!empty($config[‘root_namespace’])) {

      1. Loader::addNamespace($config['root_namespace']);

      }

      // 加载额外文件 if (!empty($config[‘extra_file_list’])) {

      1. foreach ($config['extra_file_list'] as $file) {
      2. $file = strpos($file, '.') ? $file : APP_PATH . $file . EXT;
      3. if (is_file($file) && !isset(self::$file[$file])) {
      4. include $file;
      5. self::$file[$file] = true;
      6. }
      7. }

      }

      // 设置系统时区 date_default_timezone_set($config[‘default_timezone’]);

      // 监听 app_init Hook::listen(‘app_init’);

      self::$init = true; }

      return Config::get(); } b. 查看Console::init()方法php /**

    • 初始化 Console
    • @access public
    • @param bool $run 是否运行 Console
    • @return int|Console */ public static function init($run = true) { static $console;

      if (!$console) { $config = Config::get(‘console’); // 实例化 console $console = new self($config[‘name’], $config[‘version’], $config[‘user’]);

      // 读取指令集 if (is_file(CONF_PATH . ‘command’ . EXT)) {

      1. $commands = include CONF_PATH . 'command' . EXT;
      2. if (is_array($commands)) {
      3. foreach ($commands as $command) {
      4. class_exists($command) &&
      5. is_subclass_of($command, "\\think\\console\\Command") &&
      6. $console->add(new $command()); // 注册指令
      7. }
      8. }

      } }

      return $run ? $console->run() : $console; } ```

  2. 可以看到里面有一些command变量,我们尝试打印$commands看看

    1. array(6) {
    2. [0]=>
    3. string(22) "app\admin\command\Crud"
    4. [1]=>
    5. string(22) "app\admin\command\Menu"
    6. [2]=>
    7. string(25) "app\admin\command\Install"
    8. [3]=>
    9. string(21) "app\admin\command\Min"
    10. [4]=>
    11. string(23) "app\admin\command\Addon"
    12. [5]=>
    13. string(21) "app\admin\command\Api"
    14. }
  3. 打印一下$run看看

    1. bool(true)

    说明走到了$console->run()方法。我们继续往下看

  4. 查看下run()方法

    1. /**
    2. * 执行当前的指令
    3. * @access public
    4. * @return int
    5. * @throws \Exception
    6. */
    7. public function run()
    8. {
    9. $input = new Input();
    10. $output = new Output();
    11. $this->configureIO($input, $output);
    12. try {
    13. $exitCode = $this->doRun($input, $output);
    14. } catch (\Exception $e) {
    15. if (!$this->catchExceptions) throw $e;
    16. $output->renderException($e);
    17. $exitCode = $e->getCode();
    18. if (is_numeric($exitCode)) {
    19. $exitCode = ((int) $exitCode) ?: 1;
    20. } else {
    21. $exitCode = 1;
    22. }
    23. }
    24. if ($this->autoExit) {
    25. if ($exitCode > 255) $exitCode = 255;
    26. exit($exitCode);
    27. }
    28. return $exitCode;
    29. }

    执行当前的指令,那么应该是这个方法执行没错了。

  5. 我们看到有$input,这个应该就是接受我们命令行输入的参数信息了。我们打印看看

    1. object(think\console\Input)#120 (6) {
    2. ["definition":protected]=>
    3. object(think\console\input\Definition)#121 (6) {
    4. ["arguments":"think\console\input\Definition":private]=>
    5. array(0) {
    6. }
    7. ["requiredCount":"think\console\input\Definition":private]=>
    8. int(0)
    9. ["hasAnArrayArgument":"think\console\input\Definition":private]=>
    10. bool(false)
    11. ["hasOptional":"think\console\input\Definition":private]=>
    12. bool(false)
    13. ["options":"think\console\input\Definition":private]=>
    14. array(0) {
    15. }
    16. ["shortcuts":"think\console\input\Definition":private]=>
    17. array(0) {
    18. }
    19. }
    20. ["options":protected]=>
    21. array(0) {
    22. }
    23. ["arguments":protected]=>
    24. array(0) {
    25. }
    26. ["interactive":protected]=>
    27. bool(true)
    28. ["tokens":"think\console\Input":private]=>
    29. array(1) {
    30. [0]=>
    31. string(3) "api"
    32. }
    33. ["parsed":"think\console\Input":private]=>
    34. NULL
    35. }

    其中Input下面数组的’api’参数赫赫在列,如果我们执行”php think api —force=true”,我们会发现变成了下面这样的:

    1. object(think\console\Input)#120 (6) {
    2. ["definition":protected]=>
    3. object(think\console\input\Definition)#121 (6) {
    4. ["arguments":"think\console\input\Definition":private]=>
    5. array(0) {
    6. }
    7. ["requiredCount":"think\console\input\Definition":private]=>
    8. int(0)
    9. ["hasAnArrayArgument":"think\console\input\Definition":private]=>
    10. bool(false)
    11. ["hasOptional":"think\console\input\Definition":private]=>
    12. bool(false)
    13. ["options":"think\console\input\Definition":private]=>
    14. array(0) {
    15. }
    16. ["shortcuts":"think\console\input\Definition":private]=>
    17. array(0) {
    18. }
    19. }
    20. ["options":protected]=>
    21. array(0) {
    22. }
    23. ["arguments":protected]=>
    24. array(0) {
    25. }
    26. ["interactive":protected]=>
    27. bool(true)
    28. ["tokens":"think\console\Input":private]=>
    29. array(2) {
    30. [0]=>
    31. string(3) "api"
    32. [1]=>
    33. string(12) "--force=true"
    34. }
    35. ["parsed":"think\console\Input":private]=>
    36. NULL
    37. }
  6. 我们继续接着看,执行$this->doRun()方法,我们看看doRun方法

    1. /**
    2. * 执行指令
    3. * @access public
    4. * @param Input $input 输入
    5. * @param Output $output 输出
    6. * @return int
    7. */
    8. public function doRun(Input $input, Output $output)
    9. {
    10. // 获取版本信息
    11. if (true === $input->hasParameterOption(['--version', '-V'])) {
    12. $output->writeln($this->getLongVersion());
    13. return 0;
    14. }
    15. $name = $this->getCommandName($input);
    16. // 获取帮助信息
    17. if (true === $input->hasParameterOption(['--help', '-h'])) {
    18. if (!$name) {
    19. $name = 'help';
    20. $input = new Input(['help']);
    21. } else {
    22. $this->wantHelps = true;
    23. }
    24. }
    25. if (!$name) {
    26. $name = $this->defaultCommand;
    27. $input = new Input([$this->defaultCommand]);
    28. }
    29. return $this->doRunCommand($this->find($name), $input, $output);
    30. }

    主要是对命令合法性的一些校验,我们接着往下看$this->find($name)方法

  7. $this->find($name)方法如下:

    1. /**
    2. * 查找指令
    3. * @access public
    4. * @param string $name 名称或者别名
    5. * @return Command
    6. * @throws \InvalidArgumentException
    7. */
    8. public function find($name)
    9. {
    10. $expr = preg_replace_callback('{([^:]+|)}', function ($matches) {
    11. return preg_quote($matches[1]) . '[^:]*';
    12. }, $name);
    13. $allCommands = array_keys($this->commands);
    14. $commands = preg_grep('{^' . $expr . '}', $allCommands);
    15. if (empty($commands) || count(preg_grep('{^' . $expr . '$}', $commands)) < 1) {
    16. if (false !== ($pos = strrpos($name, ':'))) {
    17. $this->findNamespace(substr($name, 0, $pos));
    18. }
    19. $message = sprintf('Command "%s" is not defined.', $name);
    20. if ($alternatives = $this->findAlternatives($name, $allCommands)) {
    21. if (1 == count($alternatives)) {
    22. $message .= "\n\nDid you mean this?\n ";
    23. } else {
    24. $message .= "\n\nDid you mean one of these?\n ";
    25. }
    26. $message .= implode("\n ", $alternatives);
    27. }
    28. throw new \InvalidArgumentException($message);
    29. }
    30. if (count($commands) > 1) {
    31. $commandList = $this->commands;
    32. $commands = array_filter($commands, function ($nameOrAlias) use ($commandList, $commands) {
    33. $commandName = $commandList[$nameOrAlias]->getName();
    34. return $commandName === $nameOrAlias || !in_array($commandName, $commands);
    35. });
    36. }
    37. $exact = in_array($name, $commands, true);
    38. if (count($commands) > 1 && !$exact) {
    39. $suggestions = $this->getAbbreviationSuggestions(array_values($commands));
    40. throw new \InvalidArgumentException(
    41. sprintf('Command "%s" is ambiguous (%s).', $name, $suggestions)
    42. );
    43. }
    44. return $this->get($exact ? $name : reset($commands));
    45. }

    最后执行了get,我们看看$this->get($name)

  8. get()方法如下 ```php /**

  • 获取指令
  • @access public
  • @param string $name 指令名称
  • @return Command
  • @throws \InvalidArgumentException */ public function get($name) { if (!isset($this->commands[$name])) {

    1. throw new \InvalidArgumentException(
    2. sprintf('The command "%s" does not exist.', $name)
    3. );

    }

    $command = $this->commands[$name];

    if ($this->wantHelps) {

    1. $this->wantHelps = false;
    2. /** @var HelpCommand $helpCommand */
    3. $helpCommand = $this->get('help');
    4. $helpCommand->setCommand($command);
    5. return $helpCommand;

    }

    return $command; } ``` 获取指令,那么我们接着往下走

  1. 执行doRunCommand()方法

    1. /**
    2. * 执行指令
    3. * @access protected
    4. * @param Command $command 指令实例
    5. * @param Input $input 输入实例
    6. * @param Output $output 输出实例
    7. * @return int
    8. * @throws \Exception
    9. */
    10. protected function doRunCommand(Command $command, Input $input, Output $output)
    11. {
    12. return $command->run($input, $output);
    13. }
  2. 然后执行到command->run()方法,我们进入thinkphp/library/think/console/Command.php可以看到

    1. /**
    2. * 执行
    3. * @param Input $input
    4. * @param Output $output
    5. * @return int
    6. * @throws \Exception
    7. * @see setCode()
    8. * @see execute()
    9. */
    10. public function run(Input $input, Output $output)
    11. {
    12. $this->input = $input;
    13. $this->output = $output;
    14. $this->getSynopsis(true);
    15. $this->getSynopsis(false);
    16. $this->mergeConsoleDefinition();
    17. try {
    18. $input->bind($this->definition);
    19. } catch (\Exception $e) {
    20. if (!$this->ignoreValidationErrors) {
    21. throw $e;
    22. }
    23. }
    24. $this->initialize($input, $output);
    25. if ($input->isInteractive()) {
    26. $this->interact($input, $output);
    27. }
    28. $input->validate();
    29. if ($this->code) {
    30. $statusCode = call_user_func($this->code, $input, $output);
    31. } else {
    32. $statusCode = $this->execute($input, $output);
    33. }
    34. return is_numeric($statusCode) ? (int) $statusCode : 0;
    35. }
  3. 走到这一步,打印statusCode为NULL,查看 $this->execute()

    1. /**
    2. * 执行指令
    3. * @param Input $input
    4. * @param Output $output
    5. * @return null|int
    6. * @throws \LogicException
    7. * @see setCode()
    8. */
    9. protected function execute(Input $input, Output $output)
    10. {
    11. throw new \LogicException('You must override the execute() method in the concrete command class.');
    12. }
  4. 看来这里的时候就是php的执行了。但是找不到文件在哪啊。 然后想起来这是基于tp5的操作,那么去看看文档吧。一搜索。发现有这个。

    创建自定义命令行

    第一步,配置command.php文件,目录在application/command.php

    1. <?php
    2. return [
    3. 'app\home\command\Test',
    4. ];

    第二步,建立命令类文件,新建application/home/command/Test.php ```php <?php namespace app\home\command;

use think\console\Command; use think\console\Input; use think\console\Output;

class Test extends Command { protected function configure() { $this->setName(‘test’)->setDescription(‘Here is the remark ‘); }

  1. protected function execute(Input $input, Output $output)
  2. {
  3. $output->writeln("TestCommand:");
  4. }

}

  1. 这个文件定义了一个叫test的命令,备注为Here is the remark,<br />执行命令会输出TestCommand。<br />第三步,测试-命令帮助-命令行下运行
  2. ```php
  3. php think

输出

  1. Think Console version 0.1
  2. Usage:
  3. command [options] [arguments]
  4. Options:
  5. -h, --help Display this help message
  6. -V, --version Display this console version
  7. -q, --quiet Do not output any message
  8. --ansi Force ANSI output
  9. --no-ansi Disable ANSI output
  10. -n, --no-interaction Do not ask any interactive question
  11. -v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
  12. Available commands:
  13. build Build Application Dirs
  14. clear Clear runtime file
  15. help Displays help for a command
  16. list Lists commands
  17. test Here is the remark
  18. make
  19. make:controller Create a new resource controller class
  20. make:model Create a new model class
  21. optimize
  22. optimize:autoload Optimizes PSR0 and PSR4 packages to be loaded with classmaps too, good for production.
  23. optimize:config Build config and common file cache.
  24. optimize:route Build route cache.
  25. optimize:schema Build database schema cache.

第四步,运行test命令

  1. php think test

输出

  1. TestCommand:

那么按照文档所述,我们去查找application/command.php文件

  1. <?php
  2. return [
  3. 'app\admin\command\Crud',
  4. 'app\admin\command\Menu',
  5. 'app\admin\command\Install',
  6. 'app\admin\command\Min',
  7. 'app\admin\command\Addon',
  8. 'app\admin\command\Api',
  9. ];

api命令对应的位置为:’app\admin\command\Api’,我们进入目录看看,可以看到Api.php文件:

  1. <?php
  2. namespace app\admin\command;
  3. use app\admin\command\Api\library\Builder;
  4. use think\Config;
  5. use think\console\Command;
  6. use think\console\Input;
  7. use think\console\input\Option;
  8. use think\console\Output;
  9. use think\Exception;
  10. class Api extends Command
  11. {
  12. protected function execute(Input $input, Output $output)
  13. {
  14. ...
  15. }
  16. }

这就是命令行执行的一些操作了。具体的业务自己去研究。