ParseApiMenus.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. <?php
  2. declare(strict_types = 1);
  3. namespace hg\apidoc\parses;
  4. use Doctrine\Common\Annotations\AnnotationException;
  5. use hg\apidoc\exception\ErrorException;
  6. use hg\apidoc\utils\DirAndFile;
  7. use hg\apidoc\utils\Helper;
  8. use hg\apidoc\utils\Lang;
  9. use ReflectionClass;
  10. use ReflectionAttribute;
  11. class ParseApiMenus
  12. {
  13. protected $config = [];
  14. //tags,当前应用/版本所有的tag
  15. protected $tags = array();
  16. //groups,当前应用/版本的分组name
  17. protected $groups = array();
  18. protected $controller_layer = "app";
  19. protected $currentApp = [];
  20. public function __construct($config)
  21. {
  22. $this->config = $config;
  23. }
  24. /**
  25. * 生成api接口数据
  26. * @param string $appKey
  27. * @param bool $isParseDetail 是否解析接口明细
  28. * @return array
  29. */
  30. public function renderApiMenus(string $appKey,bool $isParseDetail=false): array
  31. {
  32. $currentAppConfig = Helper::getCurrentAppConfig($appKey);
  33. $currentApp = $currentAppConfig['appConfig'];
  34. $this->currentApp = $currentApp;
  35. $controllers = [];
  36. if (!empty($currentApp['controllers']) && count($currentApp['controllers']) > 0) {
  37. // 配置的控制器列表
  38. $controllers = $this->getConfigControllers($currentApp['path'],$currentApp['controllers']);
  39. }else if(!empty($currentApp['path']) && is_array($currentApp['path']) && count($currentApp['path'])){
  40. // 读取paths的
  41. foreach ($currentApp['path'] as $path) {
  42. $controllersList = $this->getDirControllers($path);
  43. $controllers = array_merge($controllers,$controllersList);
  44. }
  45. } else if(!empty($currentApp['path']) && is_string($currentApp['path'])){
  46. // 默认读取path下所有的
  47. $controllers = $this->getDirControllers($currentApp['path']);
  48. }
  49. $apiData = [];
  50. if (!empty($controllers) && count($controllers) > 0) {
  51. foreach ($controllers as $class) {
  52. $classData = $this->parseController($class,$isParseDetail);
  53. if ($classData !== false) {
  54. $apiData[] = $classData;
  55. }
  56. }
  57. }
  58. // 排序
  59. $apiList = Helper::arraySortByKey($apiData);
  60. // 接口分组
  61. if (!empty($currentApp['groups'])){
  62. $apiList = ParseApiMenus::mergeApiGroup($apiList,$currentApp['groups']);
  63. }
  64. $json = array(
  65. "data" => $apiList,
  66. "tags" => $this->tags,
  67. "groups" => $this->groups,
  68. );
  69. return $json;
  70. }
  71. /**
  72. * 获取生成文档的控制器列表
  73. * @param string $path
  74. * @return array
  75. */
  76. public function getConfigControllers(string $path,$appControllers): array
  77. {
  78. $controllers = [];
  79. if (!empty($appControllers) && count($appControllers) > 0) {
  80. foreach ($appControllers as $item) {
  81. $classPath = $path."\\".$item;
  82. if ( class_exists($classPath)) {
  83. $controllers[] = $classPath;
  84. }
  85. }
  86. }
  87. return $controllers;
  88. }
  89. /**
  90. * 获取目录下的控制器列表
  91. * @param string $path
  92. * @return array
  93. */
  94. public function getDirControllers(string $path): array
  95. {
  96. if ($path) {
  97. if (strpos(APIDOC_ROOT_PATH, '/') !== false) {
  98. $pathStr = str_replace("\\", "/", $path);
  99. } else {
  100. $pathStr = $path;
  101. }
  102. $dir = APIDOC_ROOT_PATH . $pathStr;
  103. } else {
  104. $dir = APIDOC_ROOT_PATH . $this->controller_layer;
  105. }
  106. $controllers = [];
  107. if (is_dir($dir)) {
  108. $controllers = $this->scanDir($dir, $path);
  109. }
  110. return $controllers;
  111. }
  112. protected function scanDir($dir) {
  113. $classList= DirAndFile::getClassList($dir);
  114. $list=[];
  115. $configFilterController = !empty($this->config['filter_controllers']) ? $this->config['filter_controllers'] : [];
  116. $currentAppFilterController = !empty($this->currentApp['filter_controllers']) ? $this->currentApp['filter_controllers'] : [];
  117. $filterControllers = array_merge($configFilterController,$currentAppFilterController);
  118. $configFilterDir = !empty($this->config['filter_dirs']) ? $this->config['filter_dirs'] : [];
  119. $currentAppFilterDir = !empty($this->currentApp['filter_dirs']) ? $this->currentApp['filter_dirs'] : [];
  120. $filterDirList = array_merge($configFilterDir,$currentAppFilterDir);
  121. $filterDirs=[];
  122. foreach ($filterDirList as $dirItem) {
  123. $dirItemPath = DirAndFile::formatPath($dirItem,"/");
  124. $filterDirs[]=$dirItemPath;
  125. }
  126. foreach ($classList as $item) {
  127. $classNamespace = $item['name'];
  128. $isFilterDir = false;
  129. foreach ($filterDirs as $dirItem) {
  130. if (strpos($item['path'], $dirItem) !== false){
  131. $isFilterDir=true;
  132. }
  133. }
  134. if ($isFilterDir){
  135. continue;
  136. }else if (
  137. !in_array($classNamespace, $filterControllers) &&
  138. $this->config['definitions'] != $classNamespace
  139. ) {
  140. $list[] = $classNamespace;
  141. }
  142. }
  143. return $list;
  144. }
  145. public function parseController($class,$isParseDetail=false)
  146. {
  147. $refClass = new ReflectionClass($class);
  148. $classTextAnnotations = ParseAnnotation::parseTextAnnotation($refClass);
  149. $data = (new ParseAnnotation($this->config))->getClassAnnotation($refClass);
  150. if (in_array("NotParse", $classTextAnnotations) || isset($data['notParse'])) {
  151. return false;
  152. }
  153. $controllersName = $refClass->getShortName();
  154. $data['controller'] = $controllersName;
  155. $data['path'] = $class;
  156. if (!empty($data['group']) && !in_array($data['group'], $this->groups)) {
  157. $this->groups[] = $data['group'];
  158. }
  159. if (empty($data['title'])) {
  160. if (!empty($classTextAnnotations) && count($classTextAnnotations) > 0) {
  161. $data['title'] = $classTextAnnotations[0];
  162. } else {
  163. $data['title'] = $controllersName;
  164. }
  165. }
  166. $data['title'] = Lang::getLang($data['title']);
  167. $methodList = [];
  168. $data['menuKey'] = Helper::createRandKey($data['controller']);
  169. $isNotDebug = in_array("NotDebug", $classTextAnnotations) || isset($data['notDebug']);
  170. $parseApiDetail = new ParseApiDetail($this->config);
  171. foreach ($refClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $refMethod) {
  172. if ($isParseDetail){
  173. $methodItem = $parseApiDetail->parseApiMethod($refClass,$refMethod,$this->currentApp);
  174. }else{
  175. $methodItem = $this->parseApiMethod($refClass,$refMethod);
  176. }
  177. if ($methodItem===false){
  178. continue;
  179. }
  180. if ($isNotDebug) {
  181. $methodItem['notDebug'] = true;
  182. }
  183. $methodList[] = $methodItem;
  184. }
  185. $data['children'] = $methodList;
  186. if (count($methodList)===0){
  187. return false;
  188. }
  189. return $data;
  190. }
  191. protected function parseApiMethod($refClass,$refMethod){
  192. $config = $this->config;
  193. if (empty($refMethod->name)) {
  194. return false;
  195. }
  196. try {
  197. $textAnnotations = ParseAnnotation::parseTextAnnotation($refMethod);
  198. $methodInfo = (new ParseAnnotation($this->config))->getMethodAnnotation($refMethod);
  199. // 标注不解析的方法
  200. if (in_array("NotParse", $textAnnotations) || isset($methodInfo['notParse']) || empty($methodInfo)) {
  201. return false;
  202. }
  203. $methodInfo = ParseApiDetail::handleApiBaseInfo($methodInfo,$refClass->name,$refMethod->name,$textAnnotations,$config);
  204. $methodInfo['appKey'] = !empty($this->currentApp['appKey'])?$this->currentApp['appKey']:"";
  205. return Helper::getArrayValuesByKeys($methodInfo,['title','method','url','author','tag','name','menuKey','appKey']);
  206. }catch (AnnotationException $e) {
  207. throw new ErrorException($e->getMessage());
  208. }
  209. }
  210. /**
  211. * 对象分组到tree
  212. * @param $tree
  213. * @param $objectData
  214. * @param string $childrenField
  215. * @return array
  216. */
  217. public static function objtctGroupByTree($tree,$objectData,$childrenField='children'){
  218. $data = [];
  219. foreach ($tree as $node){
  220. if (!empty($node[$childrenField])){
  221. $node[$childrenField] = static::objtctGroupByTree($node[$childrenField],$objectData);
  222. }else if (!empty($objectData[$node['name']])){
  223. $node[$childrenField] = $objectData[$node['name']];
  224. }
  225. $node['menuKey'] = Helper::createRandKey( $node['name']);
  226. $data[] = $node;
  227. }
  228. return $data;
  229. }
  230. protected static function getAppGroupNames($groups){
  231. $groupNames = [];
  232. foreach ($groups as $item) {
  233. if (!empty($item['name'])){
  234. $groupNames[]=$item['name'];
  235. }
  236. if (!empty($item['children']) && count($item['children'])){
  237. $childrenNames = self::getAppGroupNames($item['children']);
  238. foreach ($childrenNames as $childrenName) {
  239. $groupNames[]=$childrenName;
  240. }
  241. }
  242. }
  243. return $groupNames;
  244. }
  245. /**
  246. * 合并接口到应用分组
  247. * @param $apiData
  248. * @param $groups
  249. * @return array
  250. */
  251. public static function mergeApiGroup($apiData,$groups){
  252. if (empty($groups) || count($apiData)<1){
  253. return $apiData;
  254. }
  255. $groupNames = static::getAppGroupNames($groups);
  256. $apiObject = [];
  257. foreach ($apiData as $controller){
  258. if (!empty($controller['group']) && in_array($controller['group'],$groupNames)){
  259. if (!empty($apiObject[$controller['group']])){
  260. $apiObject[$controller['group']][] = $controller;
  261. }else{
  262. $apiObject[$controller['group']] = [$controller];
  263. }
  264. }else{
  265. if (!empty($apiObject['notGroup'])){
  266. $apiObject['notGroup'][] = $controller;
  267. }else{
  268. $apiObject['notGroup'] = [$controller];
  269. }
  270. }
  271. }
  272. if (!empty($apiObject['notGroup'])){
  273. array_unshift($groups,['title'=>'未分组','name'=>'notGroup']);
  274. }
  275. $res = static::objtctGroupByTree($groups,$apiObject);
  276. return $res;
  277. }
  278. }