File: /www/wwwroot/ly.fwmnzf.com/application/apidoc/library/Parser.php
<?php
// +----------------------------------------------------------------------
// | Yzncms [ 御宅男工作室 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2018 http://yzncms.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: 御宅男 <530765310@qq.com>
// +----------------------------------------------------------------------
// +----------------------------------------------------------------------
// | 接口管理
// +----------------------------------------------------------------------
namespace app\apidoc\library;
use think\Db as Db5;
use think\facade\Db;
class Parser
{
protected $config = [];
/**
* 架构方法 设置参数
* @param array $config 配置参数
*/
public function __construct($config = [])
{
$this->config = array_merge($this->config, $config);
}
/**
* 解析class类的注释
* @param string $doc
* @return array
*/
public function parseClass($doc = '')
{
if ($doc == '') {
return false;
}
// Get the comment
if (preg_match('#^/\*\*(.*)\*/#s', $doc, $comment) === false) {
return false;
}
$comment = trim($comment[1]);
// Get all the lines and strip the * from the first character
if (preg_match_all('#^\s*\*(.*)#m', $comment, $lines) === false) {
return false;
}
$res = $this->parseClassLines($lines[1]);
return $res;
}
/**
* 解析class类的注释,将每条字符串,解析成key,value对象
* @param $lines
* @return array|bool
*/
private function parseClassLines($lines)
{
$desc = [];
foreach ($lines as $line) {
$line = trim($line);
if (empty($line)) {
return false; // Empty line
}
if (strpos($line, '@') === 0) {
if (strpos($line, ' ') > 0) {
// Get the parameter name
$param = substr($line, 1, strpos($line, ' ') - 1);
$value = substr($line, strlen($param) + 2); // Get the value
} else {
$param = substr($line, 1);
$value = '';
}
$desc[$param] = $value;
}
}
return $desc;
}
/**
* 解析控制器方法的注释
* @param string $doc
* @return array
*/
public function parseAction($doc = '')
{
if ($doc == '') {
return false;
}
// Get the comment
if (preg_match('#^/\*\*(.*)\*/#s', $doc, $comment) === false) {
return false;
}
$comment = trim($comment[1]);
// Get all the lines and strip the * from the first character
if (preg_match_all('#^\s*\*(.*)#m', $comment, $lines) === false) {
return false;
}
$res = $this->parseActionLines($lines[1]);
return $res;
}
/**
* 解析方法的注释,将每条字符串,解析成key,value对象,并处理
* @param $lines
* @return array|bool
*/
private function parseActionLines($lines)
{
$desc = [];
foreach ($lines as $line) {
$line = trim($line);
if (!empty($line)) {
if (strpos($line, '@') === 0) {
if (strpos($line, ' ') > 0) {
// Get the parameter name
$param = substr($line, 1, strpos($line, ' ') - 1);
$value = substr($line, strlen($param) + 2); // Get the value
} else {
$param = substr($line, 1);
$value = '';
}
if ($param == 'param') {
$valueObj = $this->formatParam($value);
if (!empty($valueObj['params']) && empty($valueObj['name'])) {
// 只配置参数,没配置name则直接值为参数
if (is_array($valueObj["params"]) && count($valueObj["params"]) > 0) {
// 数组则遍历出来
foreach ($valueObj["params"] as $paramItem) {
$desc[$param][] = $paramItem;
}
} else {
$desc[$param][] = $valueObj["params"];
}
} else if (!empty($valueObj['type']) && $valueObj['type'] === 'tree') {
// 类型为tree的
$childrenField = "children";
if (!empty($valueObj['childrenField'])) {
$childrenField = $valueObj['childrenField'];
}
$childrenDesc = "children";
if (!empty($valueObj['childrenDesc'])) {
$childrenDesc = $valueObj['childrenDesc'];
}
$valueObj['params'][] = array(
'params' => $valueObj['params'],
'name' => $childrenField,
'type' => 'array',
'desc' => $childrenDesc,
);
$desc[$param][] = $valueObj;
} else {
$desc[$param][] = $valueObj;
}
} else if ($param == 'return') {
$valueObj = $this->formatReturn($value);
if (!empty($valueObj['params']) && empty($valueObj['name'])) {
// 只配置参数,没配置name则直接值为参数
if (is_array($valueObj["params"]) && count($valueObj["params"]) > 0) {
// 数组则遍历出来
foreach ($valueObj["params"] as $paramItem) {
$desc[$param][] = $paramItem;
}
} else {
$desc[$param][] = $valueObj["params"];
}
} else if (!empty($valueObj['type']) && $valueObj['type'] === 'tree') {
// 类型为tree的
$childrenField = "children";
if (!empty($valueObj['childrenField'])) {
$childrenField = $valueObj['childrenField'];
}
$childrenDesc = "children";
if (!empty($valueObj['childrenDesc'])) {
$childrenDesc = $valueObj['childrenDesc'];
}
$valueObj['params'][] = array(
'params' => $valueObj['params'],
'name' => $childrenField,
'type' => 'array',
'desc' => $childrenDesc,
);
$desc[$param][] = $valueObj;
} else {
$desc[$param][] = $valueObj;
}
} else if ($param == 'header') {
$valueObj = $this->formatHeaders($value);
$desc[$param][] = $valueObj;
} else if ($param == 'addField') {
// 模型指定添加的字段
$valueObj = $this->formatHeaders($value);
$desc[$param][] = $valueObj;
} else {
$desc[$param] = $value;
}
}
}
}
return $desc;
}
// 处理Headers的解析
private function formatHeaders($string)
{
$string = $string . " ";
if (preg_match_all('/(\w+):(.*?)[\s\n]/s', $string, $meatchs)) {
$param = [];
foreach ($meatchs[1] as $key => $value) {
$paramKey = $meatchs[1][$key];
$value = $meatchs[2][$key];
$param[$paramKey] = $value;
}
return $param;
} else {
return '' . $string;
}
}
// 处理Param的解析
private function formatParam($string)
{
$string = $string . " ";
if (preg_match_all('/(\w+):(.*?)[\s\n]/s', $string, $meatchs)) {
$param = [];
foreach ($meatchs[1] as $key => $value) {
$paramKey = $meatchs[1][$key];
$value = $meatchs[2][$key];
if ($paramKey == "params") {
// 处理对象类型
$value = $this->parseObjectLine($value);
} else if ($paramKey == "ref") {
// 处理引用
$value = $this->parseRefLine($value, "param");
$paramKey = "params";
} else if ($paramKey == "field" && !empty($param["params"])) {
// 只取模型指定字段
$param["params"] = $this->filterModelTableField($param["params"], $value, "field");
} else if ($paramKey == "withoutField" && !empty($param["params"])) {
// 排除模型指定字段
$param["params"] = $this->filterModelTableField($param["params"], $value, "withoutField");
}
$param[$paramKey] = $value;
}
return $param;
} else {
return '' . $string;
}
}
// 处理Return的解析
private function formatReturn($string)
{
$string = $string . " ";
if (preg_match_all('/(\w+):(.*?)[\s\n]/s', $string, $meatchs)) {
$param = [];
foreach ($meatchs[1] as $key => $value) {
$paramKey = $meatchs[1][$key];
$value = $meatchs[2][$key];
if ($paramKey == "params") {
// 处理对象类型
$value = $this->parseObjectLine($value);
} else if ($paramKey == "ref") {
// 处理引用
$value = $this->parseRefLine($value, "return");
if (!empty($value) && is_array($value) && count($value) === 1) {
if (!empty($value[0]) && !empty($value[0]['params'])) {
$value = $value[0]['params'];
}
}
$paramKey = "params";
} else if ($paramKey == "field" && !empty($param["params"])) {
// 只取模型指定字段
$param["params"] = $this->filterModelTableField($param["params"], $value, "field");
} else if ($paramKey == "withoutField" && !empty($param["params"])) {
// 排除模型指定字段
$param["params"] = $this->filterModelTableField($param["params"], $value, "withoutField");
}
// if ($paramKey == "type" && $value === 'tree') {
// // 数据结构为树形结构
// $value =$param;
// }
$param[$paramKey] = $value;
}
return $param;
} else {
return '' . $string;
}
}
// 解析param参数为对象类型
public function parseObjectLine($string)
{
$string = trim($string);
if (empty($string)) {
return false; // Empty line
}
$string = $string . ",";
if (preg_match_all('/(\w+):(.*?),/s', $string, $meatchs)) {
$param = [];
foreach ($meatchs[1] as $key => $value) {
$paramKey = $meatchs[1][$key];
$value = $meatchs[2][$key];
$param[] = array("name" => $paramKey, "type" => $value);
}
return $param;
} else {
return '' . $string;
}
}
// 解析ref引用的数据,server、model、引用定义
public function parseRefLine($string, $paramKey = "")
{
$string = trim($string);
if (empty($string)) {
return false; // Empty line
}
$value = $string;
if (strpos($string, 'app\\') !== false && strpos($string, 'model\\') === false) {
// 引用服务
$value = $this->parseServer($string, $paramKey);
if (!empty($paramKey) && !empty($value) && !empty($value[$paramKey])) {
// 存在指定取值的key,去server注释中指定的值
$value = $value[$paramKey];
}
} else if (strpos($string, 'model\\') !== false) {
// 引用模型
$value = $this->parseModel($string);
} else if (strpos($string, 'definitions\\') !== false) {
// 引用定义
$value = $this->parseDefinitions($string);
if (!empty($paramKey) && !empty($value) && !empty($value[$paramKey])) {
// 存在指定取值的key,去server注释中指定的值
$value = $value[$paramKey];
}
}
return $value;
}
// 解析服务的注释
public function parseServer($path, $paramKey)
{
$modelClassArr = explode("\\", $path);
$modelActionName = $modelClassArr[count($modelClassArr) - 1];
$modelClassName = $modelClassArr[count($modelClassArr) - 2];
unset($modelClassArr[count($modelClassArr) - 1]);
$modelClassPath = implode("\\", $modelClassArr);
$classReflect = new \ReflectionClass($modelClassPath);
$modelActionName = trim($modelActionName);
$methodAction = $classReflect->getMethod($modelActionName);
$doc_str = $methodAction->getDocComment();
$action_doc = $this->parseParam($doc_str);
return $action_doc;
}
// 解析模型的注释
public function parseModel($path)
{
$modelClassArr = explode("\\", $path);
$modelActionName = $modelClassArr[count($modelClassArr) - 1];
$modelClassName = $modelClassArr[count($modelClassArr) - 2];
unset($modelClassArr[count($modelClassArr) - 1]);
$modelClassPath = implode("\\", $modelClassArr);
$classReflect = new \ReflectionClass($modelClassPath);
$modelActionName = trim($modelActionName);
$methodAction = $classReflect->getMethod($modelActionName);
//获取模型方法的注释
$doc_str = $methodAction->getDocComment();
//解析注释
$action_doc = $this->parseParam($doc_str);
// 获取表字段
$model = $this->getModel($methodAction, $modelClassName);
$table = $this->getTableDocument($model);
//过滤field
if (!empty($action_doc) && !empty($action_doc['field'])) {
$table = $this->filterModelTableField($table, $action_doc['field'], "field");
} else if (!empty($action_doc) && !empty($action_doc['withoutField'])) {
$table = $this->filterModelTableField($table, $action_doc['withoutField'], "withoutField");
}
if (!empty($action_doc) && !empty($action_doc['addField'])) {
$table = array_merge($table, $action_doc['addField']);
}
return $table;
}
// 获取模型
private function getModel($method, $modelClassName)
{
if (!empty($method->class)) {
$relationModelClass = $this->getIncludeClassName($method->class, $modelClassName);
if ($relationModelClass) {
$modelInstance = new $relationModelClass();
return $modelInstance;
} else {
return null;
}
} else {
return null;
}
}
// 过滤模型字段
public function filterModelTableField($params, $keys, $type = "field")
{
$modelParams = [];
$fieldArr = explode(',', $keys);
foreach ($params as $modelParam) {
if (!empty($modelParam['name']) && in_array($modelParam['name'], $fieldArr) && $type == "field") {
// 取指定字段
$modelParams[] = $modelParam;
} else if (!(!empty($modelParam['name']) && in_array($modelParam['name'], $fieldArr)) && $type == "withoutField") {
// 排除指定字段
$modelParams[] = $modelParam;
}
}
return $modelParams;
}
// 解析定义的注释
public function parseDefinitions($path)
{
$modelClassArr = explode("\\", $path);
$modelActionName = $modelClassArr[count($modelClassArr) - 1];
$definitionsPath = !empty($this->config['definitions']) ? $this->config['definitions'] : "hg\apidoc\Definitions";
$classReflect = new \ReflectionClass($definitionsPath);
$modelActionName = trim($modelActionName);
$methodAction = $classReflect->getMethod($modelActionName);
$doc_str = $methodAction->getDocComment();
$action_doc = $this->parseParam($doc_str);
return $action_doc;
}
/**
* 解析参数的注释,server的注释解析
* @param string $doc
* @return array
*/
public function parseParam($doc = '')
{
if ($doc == '') {
return false;
}
// Get the comment
if (preg_match('#^/\*\*(.*)\*/#s', $doc, $comment) === false) {
return false;
}
$comment = trim($comment[1]);
// Get all the lines and strip the * from the first character
if (preg_match_all('#^\s*\*(.*)#m', $comment, $lines) === false) {
return false;
}
$res = $this->parseActionLines($lines[1]);
return $res;
}
/**
* 根据模型获取表的注释
* @param Model $model
* @return array
*/
public function getTableDocument($model)
{
$tp_version = \think\facade\App::version();
if (substr($tp_version, 0, 2) == '5.') {
$createSQL = Db5::query("show create table " . $model->getTable())[0]['Create Table'];
} else {
$createSQL = Db::query("show create table " . $model->getTable())[0]['Create Table'];
}
preg_match_all("#`(.*?)`(.*?),#", $createSQL, $matches);
$fields = $matches[1];
$types = $matches[2];
$fieldComment = [];
//组织注释
for ($i = 0; $i < count($matches[0]); $i++) {
$key = $fields[$i];
$typeString = $types[$i];
$typeString = trim($typeString);
$typeArr = explode(' ', $typeString);
$type = $typeArr[0];
$default = "";
$require = "0";
$desc = "";
if (strpos($typeString, 'COMMENT') !== false) {
// 存在字段注释
preg_match_all("#COMMENT\s*'(.*?)'#", $typeString, $edscs);
if (!empty($edscs[1]) && !empty($edscs[1][0])) {
$desc = $edscs[1][0];
}
}
if (strpos($typeString, 'DEFAULT') !== false) {
// 存在字段默认值
preg_match_all("#DEFAULT\s*'(.*?)'#", $typeString, $defaults);
if (!empty($defaults[1]) && !empty($defaults[1][0])) {
$default = $defaults[1][0];
}
}
if (strpos($typeString, 'NOT NULL') !== false) {
// 必填字段
$require = "1";
}
$fieldComment[] = [
"name" => $key,
"type" => $type,
"desc" => $desc,
"default" => $default,
"require" => $require,
// "str"=>$createSQL
];
}
return $fieldComment;
}
/**
* 获取类文件的内容
* @param $className
* @return mixed
* @throws \Exception
*/
protected function getClassFileContent($className)
{
if (class_exists($className)) {
$classReflect = new \ReflectionClass($className);
} else {
throw new \Exception("类不存在", '1');
}
if (!isset($this->classFileMaps[$className])) {
$this->classFileMaps[$className] = file_get_contents($classReflect->getFileName());
}
return $this->classFileMaps[$className];
}
public function getIncludeClassName($mainClass, $class)
{
$classFile = $this->getClassFileContent($mainClass);
$pattern = "/use\s*(app.*?\\\\$class)/";
if (preg_match($pattern, $classFile, $matches)) {
return $matches[1];
} else {
$classReflect = new \ReflectionClass($mainClass);
$possibleClass = $classReflect->getNamespaceName() . "\\" . $class;
if (class_exists($possibleClass)) {
return $possibleClass;
} else {
return "";
}
}
}
}