最近有一个需求,统计所有的api,生成文档页面,然后浏览器访问该页面,可以查看所有的接口请求方式,入参与返回参数,并且可以在线测试。这时候想起来了fastadmin有一个根据命令行执行生成文档的操作。
php think api执行的文件是application/admin/command/Api.php中的文件。下面的操作大部分都是查找如何调用的,但是没找到如何实现调用的。
我们来看看:
常用命令:
//一键生成API文档php think api --force=true//指定https://www.example.com为API接口请求域名,默认为空php think api -u https://www.example.com --force=true//输出自定义文件为myapi.html,默认为api.htmlphp think api -o myapi.html --force=true//修改API模板为mytemplate.html,默认为index.htmlphp think api -e mytemplate.html --force=true//修改标题为FastAdmin,作者为作者php think api -t FastAdmin -a Karson --force=true//查看API接口命令行帮助php think api -h
参数参考:
-u, --url[=URL] 默认API请求URL地址 [default: ""]-m, --module[=MODULE] 模块名(admin/index/api) [default: "api"]-o, --output[=OUTPUT] 输出文件 [default: "api.html"]-e, --template[=TEMPLATE] 模板文件 [default: "index.html"]-f, --force[=FORCE] 覆盖模式 [default: false]-t, --title[=TITLE] 文档标题 [default: "FastAdmin"]-a, --author[=AUTHOR] 文档作者 [default: "FastAdmin"]-c, --class[=CLASS] 扩展类 (multiple values allowed)-l, --language[=LANGUAGE] 语言 [default: "zh-cn"]
我们执行命令行看看
php think apiBuild Successed!
这个时候我们可以看到 public目录下 多出了个 api.html页面,我们访问的时候,可以看到生成了一个文档的页面。
那么我们来看看这个命令是如何实现的
php think api
- 首先,php文件执行了think文件,我们打开think文件看看 ```php // 定义项目路径 define(‘APPPATH’, _DIR . ‘/application/‘);
// 加载框架引导文件 require ‘./thinkphp/console.php’;
2. 这里引入了console.php文件,我们进入查看一下```phpnamespace think;// ThinkPHP 引导文件// 加载基础文件require __DIR__ . '/base.php';// 执行应用App::initCommon();Console::init();
这里初始化了APP类和Console类。
- 查看App::initCommon()方法,如下,返回了配置信息 ```php /**
- 初始化应用,并返回配置信息
- @access public
@return array */ public static function initCommon() { if (empty(self::$init)) { if (defined(‘APP_NAMESPACE’)) {
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) {
ini_set('display_errors', 'Off');
} elseif (!IS_CLI) {
// 重新申请一块比较大的 bufferif (ob_get_level() > 0) {$output = ob_get_clean();}ob_start();if (!empty($output)) {echo $output;}
}
if (!empty($config[‘root_namespace’])) {
Loader::addNamespace($config['root_namespace']);
}
// 加载额外文件 if (!empty($config[‘extra_file_list’])) {
foreach ($config['extra_file_list'] as $file) {$file = strpos($file, '.') ? $file : APP_PATH . $file . EXT;if (is_file($file) && !isset(self::$file[$file])) {include $file;self::$file[$file] = true;}}
}
// 设置系统时区 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)) {
$commands = include CONF_PATH . 'command' . EXT;if (is_array($commands)) {foreach ($commands as $command) {class_exists($command) &&is_subclass_of($command, "\\think\\console\\Command") &&$console->add(new $command()); // 注册指令}}
} }
return $run ? $console->run() : $console; } ```
可以看到里面有一些command变量,我们尝试打印$commands看看
array(6) {[0]=>string(22) "app\admin\command\Crud"[1]=>string(22) "app\admin\command\Menu"[2]=>string(25) "app\admin\command\Install"[3]=>string(21) "app\admin\command\Min"[4]=>string(23) "app\admin\command\Addon"[5]=>string(21) "app\admin\command\Api"}
打印一下$run看看
bool(true)
说明走到了$console->run()方法。我们继续往下看
查看下run()方法
/*** 执行当前的指令* @access public* @return int* @throws \Exception*/public function run(){$input = new Input();$output = new Output();$this->configureIO($input, $output);try {$exitCode = $this->doRun($input, $output);} catch (\Exception $e) {if (!$this->catchExceptions) throw $e;$output->renderException($e);$exitCode = $e->getCode();if (is_numeric($exitCode)) {$exitCode = ((int) $exitCode) ?: 1;} else {$exitCode = 1;}}if ($this->autoExit) {if ($exitCode > 255) $exitCode = 255;exit($exitCode);}return $exitCode;}
执行当前的指令,那么应该是这个方法执行没错了。
我们看到有$input,这个应该就是接受我们命令行输入的参数信息了。我们打印看看
object(think\console\Input)#120 (6) {["definition":protected]=>object(think\console\input\Definition)#121 (6) {["arguments":"think\console\input\Definition":private]=>array(0) {}["requiredCount":"think\console\input\Definition":private]=>int(0)["hasAnArrayArgument":"think\console\input\Definition":private]=>bool(false)["hasOptional":"think\console\input\Definition":private]=>bool(false)["options":"think\console\input\Definition":private]=>array(0) {}["shortcuts":"think\console\input\Definition":private]=>array(0) {}}["options":protected]=>array(0) {}["arguments":protected]=>array(0) {}["interactive":protected]=>bool(true)["tokens":"think\console\Input":private]=>array(1) {[0]=>string(3) "api"}["parsed":"think\console\Input":private]=>NULL}
其中Input下面数组的’api’参数赫赫在列,如果我们执行”php think api —force=true”,我们会发现变成了下面这样的:
object(think\console\Input)#120 (6) {["definition":protected]=>object(think\console\input\Definition)#121 (6) {["arguments":"think\console\input\Definition":private]=>array(0) {}["requiredCount":"think\console\input\Definition":private]=>int(0)["hasAnArrayArgument":"think\console\input\Definition":private]=>bool(false)["hasOptional":"think\console\input\Definition":private]=>bool(false)["options":"think\console\input\Definition":private]=>array(0) {}["shortcuts":"think\console\input\Definition":private]=>array(0) {}}["options":protected]=>array(0) {}["arguments":protected]=>array(0) {}["interactive":protected]=>bool(true)["tokens":"think\console\Input":private]=>array(2) {[0]=>string(3) "api"[1]=>string(12) "--force=true"}["parsed":"think\console\Input":private]=>NULL}
我们继续接着看,执行$this->doRun()方法,我们看看doRun方法
/*** 执行指令* @access public* @param Input $input 输入* @param Output $output 输出* @return int*/public function doRun(Input $input, Output $output){// 获取版本信息if (true === $input->hasParameterOption(['--version', '-V'])) {$output->writeln($this->getLongVersion());return 0;}$name = $this->getCommandName($input);// 获取帮助信息if (true === $input->hasParameterOption(['--help', '-h'])) {if (!$name) {$name = 'help';$input = new Input(['help']);} else {$this->wantHelps = true;}}if (!$name) {$name = $this->defaultCommand;$input = new Input([$this->defaultCommand]);}return $this->doRunCommand($this->find($name), $input, $output);}
主要是对命令合法性的一些校验,我们接着往下看$this->find($name)方法
$this->find($name)方法如下:
/*** 查找指令* @access public* @param string $name 名称或者别名* @return Command* @throws \InvalidArgumentException*/public function find($name){$expr = preg_replace_callback('{([^:]+|)}', function ($matches) {return preg_quote($matches[1]) . '[^:]*';}, $name);$allCommands = array_keys($this->commands);$commands = preg_grep('{^' . $expr . '}', $allCommands);if (empty($commands) || count(preg_grep('{^' . $expr . '$}', $commands)) < 1) {if (false !== ($pos = strrpos($name, ':'))) {$this->findNamespace(substr($name, 0, $pos));}$message = sprintf('Command "%s" is not defined.', $name);if ($alternatives = $this->findAlternatives($name, $allCommands)) {if (1 == count($alternatives)) {$message .= "\n\nDid you mean this?\n ";} else {$message .= "\n\nDid you mean one of these?\n ";}$message .= implode("\n ", $alternatives);}throw new \InvalidArgumentException($message);}if (count($commands) > 1) {$commandList = $this->commands;$commands = array_filter($commands, function ($nameOrAlias) use ($commandList, $commands) {$commandName = $commandList[$nameOrAlias]->getName();return $commandName === $nameOrAlias || !in_array($commandName, $commands);});}$exact = in_array($name, $commands, true);if (count($commands) > 1 && !$exact) {$suggestions = $this->getAbbreviationSuggestions(array_values($commands));throw new \InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $name, $suggestions));}return $this->get($exact ? $name : reset($commands));}
最后执行了get,我们看看$this->get($name)
get()方法如下 ```php /**
- 获取指令
- @access public
- @param string $name 指令名称
- @return Command
@throws \InvalidArgumentException */ public function get($name) { if (!isset($this->commands[$name])) {
throw new \InvalidArgumentException(sprintf('The command "%s" does not exist.', $name));
}
$command = $this->commands[$name];
if ($this->wantHelps) {
$this->wantHelps = false;/** @var HelpCommand $helpCommand */$helpCommand = $this->get('help');$helpCommand->setCommand($command);return $helpCommand;
}
return $command; } ``` 获取指令,那么我们接着往下走
执行doRunCommand()方法
/*** 执行指令* @access protected* @param Command $command 指令实例* @param Input $input 输入实例* @param Output $output 输出实例* @return int* @throws \Exception*/protected function doRunCommand(Command $command, Input $input, Output $output){return $command->run($input, $output);}
然后执行到command->run()方法,我们进入thinkphp/library/think/console/Command.php可以看到
/*** 执行* @param Input $input* @param Output $output* @return int* @throws \Exception* @see setCode()* @see execute()*/public function run(Input $input, Output $output){$this->input = $input;$this->output = $output;$this->getSynopsis(true);$this->getSynopsis(false);$this->mergeConsoleDefinition();try {$input->bind($this->definition);} catch (\Exception $e) {if (!$this->ignoreValidationErrors) {throw $e;}}$this->initialize($input, $output);if ($input->isInteractive()) {$this->interact($input, $output);}$input->validate();if ($this->code) {$statusCode = call_user_func($this->code, $input, $output);} else {$statusCode = $this->execute($input, $output);}return is_numeric($statusCode) ? (int) $statusCode : 0;}
走到这一步,打印statusCode为NULL,查看 $this->execute()
/*** 执行指令* @param Input $input* @param Output $output* @return null|int* @throws \LogicException* @see setCode()*/protected function execute(Input $input, Output $output){throw new \LogicException('You must override the execute() method in the concrete command class.');}
看来这里的时候就是php的执行了。但是找不到文件在哪啊。 然后想起来这是基于tp5的操作,那么去看看文档吧。一搜索。发现有这个。
创建自定义命令行
第一步,配置command.php文件,目录在application/command.php
<?phpreturn ['app\home\command\Test',];
第二步,建立命令类文件,新建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 ‘); }
protected function execute(Input $input, Output $output){$output->writeln("TestCommand:");}
}
这个文件定义了一个叫test的命令,备注为Here is the remark,<br />执行命令会输出TestCommand。<br />第三步,测试-命令帮助-命令行下运行```phpphp think
输出
Think Console version 0.1Usage:command [options] [arguments]Options:-h, --help Display this help message-V, --version Display this console version-q, --quiet Do not output any message--ansi Force ANSI output--no-ansi Disable ANSI output-n, --no-interaction Do not ask any interactive question-v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugAvailable commands:build Build Application Dirsclear Clear runtime filehelp Displays help for a commandlist Lists commandstest Here is the remarkmakemake:controller Create a new resource controller classmake:model Create a new model classoptimizeoptimize:autoload Optimizes PSR0 and PSR4 packages to be loaded with classmaps too, good for production.optimize:config Build config and common file cache.optimize:route Build route cache.optimize:schema Build database schema cache.
第四步,运行test命令
php think test
输出
TestCommand:
那么按照文档所述,我们去查找application/command.php文件
<?phpreturn ['app\admin\command\Crud','app\admin\command\Menu','app\admin\command\Install','app\admin\command\Min','app\admin\command\Addon','app\admin\command\Api',];
api命令对应的位置为:’app\admin\command\Api’,我们进入目录看看,可以看到Api.php文件:
<?phpnamespace app\admin\command;use app\admin\command\Api\library\Builder;use think\Config;use think\console\Command;use think\console\Input;use think\console\input\Option;use think\console\Output;use think\Exception;class Api extends Command{protected function execute(Input $input, Output $output){...}}
这就是命令行执行的一些操作了。具体的业务自己去研究。
