openapi 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. #!/usr/bin/env php
  2. <?php
  3. use OpenApi\Logger;
  4. use OpenApi\Analysis;
  5. use const OpenApi\UNDEFINED;
  6. use const OpenApi\COLOR_RED;
  7. use const OpenApi\COLOR_YELLOW;
  8. use const OpenApi\COLOR_STOP;
  9. error_reporting(E_ALL);
  10. // Possible options and their default values.
  11. $options = [
  12. 'output' => false,
  13. 'format' => 'auto',
  14. 'exclude' => [],
  15. 'pattern' => '*.php',
  16. 'bootstrap' => false,
  17. 'help' => false,
  18. 'debug' => false,
  19. 'processor' => [],
  20. ];
  21. $aliases = [
  22. 'o' => 'output',
  23. 'e' => 'exclude',
  24. 'n' => 'pattern',
  25. 'b' => 'bootstrap',
  26. 'h' => 'help',
  27. 'd' => 'debug',
  28. 'p' => 'processor',
  29. 'f' => 'format'
  30. ];
  31. $needsArgument = [
  32. 'output',
  33. 'format',
  34. 'exclude',
  35. 'pattern',
  36. 'bootstrap',
  37. 'processor',
  38. ];
  39. $paths = [];
  40. $error = false;
  41. define('OpenApi\COLOR_RED', "\033[31m");
  42. define('OpenApi\COLOR_YELLOW', "\033[33m");
  43. define('OpenApi\COLOR_STOP', "\033[0m");
  44. try {
  45. // Parse cli arguments
  46. for ($i = 1; $i < $argc; $i++) {
  47. $arg = $argv[$i];
  48. if (substr($arg, 0, 2) === '--') { // longopt
  49. $option = substr($arg, 2);
  50. } elseif ($arg[0] === '-') { // shortopt
  51. if (array_key_exists(substr($arg, 1), $aliases)) {
  52. $option = $aliases[$arg[1]];
  53. } else {
  54. throw new Exception('Unknown option: "' . $arg . '"');
  55. }
  56. } else {
  57. $paths[] = $arg;
  58. continue;
  59. }
  60. if (array_key_exists($option, $options) === false) {
  61. throw new Exception('Unknown option: "' . $arg . '"');
  62. }
  63. if (in_array($option, $needsArgument)) {
  64. if (empty($argv[$i + 1]) || $argv[$i + 1][0] === '-') {
  65. throw new Exception('Missing argument for "' . $arg . '"');
  66. }
  67. if (is_array($options[$option])) {
  68. $options[$option][] = $argv[$i + 1];
  69. } else {
  70. $options[$option] = $argv[$i + 1];
  71. }
  72. $i++;
  73. } else {
  74. $options[$option] = true;
  75. }
  76. }
  77. } catch (Exception $e) {
  78. $error = $e->getMessage();
  79. }
  80. if (!$error && $options['bootstrap']) {
  81. if (is_readable($options['bootstrap']) === false) {
  82. $error = 'Invalid `--bootstrap` value: "'.$options['bootstrap'].'"';
  83. } else {
  84. require_once($options['bootstrap']);
  85. }
  86. }
  87. if (count($paths) === 0) {
  88. $error = 'Specify at least one path.';
  89. }
  90. if ($options['help'] === false && $error) {
  91. error_log('');
  92. error_log(COLOR_RED.'Error: '.$error.COLOR_STOP);
  93. $options['help'] = true; // Show help
  94. }
  95. if ($options['help']) {
  96. $help = <<<EOF
  97. Usage: openapi [--option value] [/path/to/project ...]
  98. Options:
  99. --output (-o) Path to store the generated documentation.
  100. ex: --output openapi.yaml
  101. --exclude (-e) Exclude path(s).
  102. ex: --exclude vendor,library/Zend
  103. --pattern (-n) Pattern of files to scan.
  104. ex: --pattern "*.php" or --pattern "/\.(phps|php)$/"
  105. --bootstrap (-b) Bootstrap a php file for defining constants, etc.
  106. ex: --bootstrap config/constants.php
  107. --processor Register an additional processor.
  108. --format Force yaml or json.
  109. --debug Show additional error information.
  110. --help (-h) Display this help message.
  111. EOF;
  112. error_log($help);
  113. exit(1);
  114. }
  115. if (class_exists(Logger::class) === false) {
  116. if (file_exists(__DIR__.'/../vendor/autoload.php')) { // cloned / dev environment?
  117. require_once(__DIR__.'/../vendor/autoload.php');
  118. } else {
  119. require_once(realpath(__DIR__.'/../../../').'/autoload.php');
  120. }
  121. }
  122. $errorTypes = [
  123. E_ERROR => 'Error',
  124. E_WARNING => 'Warning',
  125. E_PARSE => 'Parser error',
  126. E_NOTICE => 'Notice',
  127. E_STRICT => 'Strict',
  128. E_DEPRECATED => 'Deprecated',
  129. E_CORE_ERROR => 'Error(Core)',
  130. E_CORE_WARNING => 'Warning(Core)',
  131. E_COMPILE_ERROR => 'Error(compile)',
  132. E_COMPILE_WARNING => 'Warning(Compile)',
  133. E_RECOVERABLE_ERROR => 'Error(Recoverable)',
  134. E_USER_ERROR => 'Error',
  135. E_USER_WARNING => 'Warning',
  136. E_USER_NOTICE => 'Notice',
  137. E_USER_DEPRECATED => 'Deprecated',
  138. ];
  139. set_error_handler(function ($errno, $errstr, $file, $line) use ($errorTypes, $options) {
  140. if (!(error_reporting() & $errno)) {
  141. return; // This error code is not included in error_reporting
  142. }
  143. $type = array_key_exists($errno, $errorTypes) ? $errorTypes[$errno] : 'Error';
  144. $color = (substr($type, 0, 5) === 'Error') ? COLOR_RED: COLOR_YELLOW;
  145. error_log(COLOR_RED.$type. ': '.$errstr.COLOR_STOP);
  146. if ($options['debug']) {
  147. error_log(' in '.$file.' on line '.$line);
  148. }
  149. if (substr($type, 0, 5) === 'Error') {
  150. exit($errno);
  151. }
  152. });
  153. set_exception_handler(function ($exception) use ($options) {
  154. if ($options['debug']) {
  155. error_log($exception);
  156. } else {
  157. error_log(COLOR_RED.'Exception: '.$exception->getMessage().COLOR_STOP);
  158. // if ($options['debug']) {
  159. // error_log(' in '.$exception->getFile().' on line '.$exception->getLine());
  160. // }
  161. }
  162. exit($exception->getCode() ?: 1);
  163. });
  164. $exit = 0;
  165. Logger::getInstance()->log = function ($entry, $type) use ($options, &$exit) {
  166. $exit = 1;
  167. if ($type === E_USER_NOTICE) {
  168. $type = '';
  169. $color = COLOR_YELLOW;
  170. } else {
  171. $type = 'Warning: ';
  172. $color = COLOR_RED;
  173. }
  174. if ($entry instanceof Exception) {
  175. error_log(COLOR_RED."Error: " . $entry->getMessage().COLOR_STOP);
  176. if ($options['debug']) {
  177. error_log('Stack trace:'.PHP_EOL.$entry->getTraceAsString());
  178. }
  179. } else {
  180. error_log($color. $type . $entry.COLOR_STOP);
  181. if ($options['debug']) {
  182. // Show backtrace in debug mode
  183. $e = (string)(new Exception('trace'));
  184. $trace = explode("\n", substr($e, strpos($e, 'Stack trace:')));
  185. foreach ($trace as $i => $entry) {
  186. if ($i === 0) {
  187. error_log($entry);
  188. }
  189. if ($i <= 3) {
  190. continue;
  191. }
  192. preg_match('/#([0-9]+) (.*)$/', $entry, $match);
  193. error_log('#' .($match[1] - 2).' '.$match[2]);
  194. }
  195. }
  196. }
  197. };
  198. $exclude = null;
  199. if ($options['exclude']) {
  200. $exclude = $options['exclude'];
  201. if (strpos($exclude[0], ',') !== false) {
  202. $exploded = explode(',', $exclude[0]);
  203. error_log(COLOR_RED.'Comma-separated exclude paths are deprecated, use multiple --exclude statements: --exclude '.$exploded[0].' --exclude '.$exploded[1]).COLOR_STOP;
  204. $exclude[0] = array_shift($exploded);
  205. $exclude = array_merge($exclude, $exploded);
  206. }
  207. }
  208. $pattern = "*.php";
  209. if ($options['pattern']) {
  210. $pattern = $options['pattern'];
  211. }
  212. foreach ($options["processor"] as $processor) {
  213. $class = '\OpenApi\Processors\\'.$processor;
  214. if (class_exists($class)) {
  215. $processor = new $class();
  216. } elseif (class_exists($processor)) {
  217. $processor = new $processor();
  218. }
  219. Analysis::registerProcessor($processor);
  220. }
  221. $openapi = OpenApi\scan($paths, ['exclude' => $exclude, 'pattern' => $pattern]);
  222. if ($exit !== 0) {
  223. error_log('');
  224. }
  225. if ($options['output'] === false) {
  226. if (strtolower($options['format']) === 'json') {
  227. echo $openapi->toJson();
  228. } else {
  229. echo $openapi->toYaml();
  230. }
  231. echo "\n";
  232. } else {
  233. if (is_dir($options['output'])) {
  234. $options['output'] .= '/openapi.yaml';
  235. }
  236. $openapi->saveAs($options['output'], $options['format']);
  237. }
  238. exit($exit);