最近有一个需求,统计所有的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.html
php think api -o myapi.html --force=true
//修改API模板为mytemplate.html,默认为index.html
php 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 api
Build Successed!
这个时候我们可以看到 public目录下 多出了个 api.html页面,我们访问的时候,可以看到生成了一个文档的页面。
那么我们来看看这个命令是如何实现的
php think api
- 首先,php文件执行了think文件,我们打开think文件看看 ```php // 定义项目路径 define(‘APPPATH’, _DIR . ‘/application/‘);
// 加载框架引导文件 require ‘./thinkphp/console.php’;
2. 这里引入了console.php文件,我们进入查看一下
```php
namespace 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) {
// 重新申请一块比较大的 buffer
if (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
<?php
return [
'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 />第三步,测试-命令帮助-命令行下运行
```php
php think
输出
Think Console version 0.1
Usage:
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 debug
Available commands:
build Build Application Dirs
clear Clear runtime file
help Displays help for a command
list Lists commands
test Here is the remark
make
make:controller Create a new resource controller class
make:model Create a new model class
optimize
optimize: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文件
<?php
return [
'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文件:
<?php
namespace 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)
{
...
}
}
这就是命令行执行的一些操作了。具体的业务自己去研究。