11#!/usr/bin/env php
22<?php
33
4- use OpenApi \Analysers \AttributeAnnotationFactory ;
5- use OpenApi \Analysers \DocBlockAnnotationFactory ;
6- use OpenApi \Analysers \ReflectionAnalyser ;
7- use OpenApi \Annotations \OpenApi ;
4+ use OpenApi \Console \GenerateCommand ;
85use OpenApi \Generator ;
9- use OpenApi \SourceFinder ;
10- use OpenApi \Loggers \ConsoleLogger ;
6+ use Symfony \Component \Console \Application ;
7+ use Symfony \Component \Console \Input \ArgvInput ;
8+ use Symfony \Component \Console \Logger \ConsoleLogger ;
9+ use Symfony \Component \Console \Output \ConsoleOutput ;
1110
1211if (class_exists (Generator::class) === false ) {
1312 if (file_exists (__DIR__ .'/../vendor/autoload.php ' )) { // cloned / dev environment?
@@ -17,254 +16,31 @@ if (class_exists(Generator::class) === false) {
1716 }
1817}
1918
20- error_reporting (E_ALL );
21-
22- // Possible options and their default values.
23- $ options = [
24- 'config ' => [],
25- 'defaults ' => false ,
26- 'output ' => false ,
27- 'format ' => 'auto ' ,
28- 'exclude ' => [],
29- 'pattern ' => '*.php ' ,
30- 'bootstrap ' => [],
31- 'help ' => false ,
32- 'debug ' => false ,
33- 'add-processor ' => [],
34- 'remove-processor ' => [],
35- 'version ' => null ,
36- ];
37- $ aliases = [
38- 'c ' => 'config ' ,
39- 'D ' => 'defaults ' ,
40- 'o ' => 'output ' ,
41- 'e ' => 'exclude ' ,
42- 'n ' => 'pattern ' ,
43- 'b ' => 'bootstrap ' ,
44- 'h ' => 'help ' ,
45- 'd ' => 'debug ' ,
46- 'a ' => 'add-processor ' ,
47- 'r ' => 'remove-processor ' ,
48- 'f ' => 'format '
49- ];
50- $ needsArgument = [
51- 'config ' ,
52- 'output ' ,
53- 'format ' ,
54- 'exclude ' ,
55- 'pattern ' ,
56- 'bootstrap ' ,
57- 'add-processor ' ,
58- 'remove-processor ' ,
59- 'version ' ,
60- ];
61- $ paths = [];
62- $ error = false ;
63-
64- try {
65- // Parse cli arguments
66- for ($ i = 1 ; $ i < $ argc ; $ i ++) {
67- $ arg = $ argv [$ i ];
68-
69- if (substr ($ arg , 0 , 2 ) === '-- ' ) {
70- // longopt
71- $ option = substr ($ arg , 2 );
72- } elseif ($ arg [0 ] === '- ' ) {
73- // shortopt
74- if (array_key_exists (substr ($ arg , 1 ), $ aliases )) {
75- $ option = $ aliases [$ arg [1 ]];
76- } else {
77- throw new Exception ('Unknown option: " ' . $ arg . '" ' );
78- }
79- } else {
80- $ paths [] = $ arg ;
81- continue ;
82- }
83-
84- if (false === array_key_exists ($ option , $ options )) {
85- throw new Exception ('Unknown option: " ' . $ arg . '" ' );
86- }
87-
88- if (in_array ($ option , $ needsArgument )) {
89- if (empty ($ argv [$ i + 1 ]) || $ argv [$ i + 1 ][0 ] === '- ' ) {
90- throw new Exception ('Missing argument for " ' . $ arg . '" ' );
91- }
92- if (is_array ($ options [$ option ])) {
93- $ options [$ option ][] = $ argv [$ i + 1 ];
94- } else {
95- $ options [$ option ] = $ argv [$ i + 1 ];
96- }
97- $ i ++;
98- } else {
99- $ options [$ option ] = true ;
100- }
101- }
102- } catch (\Exception $ e ) {
103- $ error = $ e ->getMessage ();
104- }
105-
106- $ logger = new ConsoleLogger ($ options ['debug ' ]);
107-
108- if (!$ error && $ options ['bootstrap ' ]) {
109- foreach ($ options ['bootstrap ' ] as $ bootstrap ) {
110- $ filenames = glob ($ bootstrap );
111- if (false === $ filenames ) {
112- $ error = 'Invalid `--bootstrap` value: " ' . $ bootstrap . '" ' ;
113- break ;
19+ $ input = new class extends ArgvInput
20+ {
21+ public function hasParameterOption (array |string $ values , bool $ onlyParams = false ): bool
22+ {
23+ // Skip the built-in version option check
24+ // thus the command can use it for its own purpose
25+ if (['--version ' , '-V ' ] === $ values ) {
26+ return false ;
11427 }
115- foreach ($ filenames as $ filename ) {
116- if ($ options ['debug ' ]) {
117- $ logger ->debug ('Bootstrapping: ' . $ filename );
118- }
119- require_once ($ filename );
120- }
121- }
122- }
123-
124- if ($ options ['defaults ' ]) {
125- $ logger ->info ('Default config ' );
126- $ logger ->info (json_encode ((new Generator ())->getDefaultConfig (), JSON_PRETTY_PRINT ));
127- exit (1 );
128- }
129-
130- if (count ($ paths ) === 0 ) {
131- $ error = 'Specify at least one path. ' ;
132- }
133-
134- if ($ options ['help ' ] === false && $ error ) {
135- $ logger ->error ('' , ['prefix ' => '' ]);
136- $ logger ->error ($ error );
137- // Show help
138- $ options ['help ' ] = true ;
139- }
140- $ defaultVersion = OpenApi::DEFAULT_VERSION ;
141- if ($ options ['help ' ]) {
142- $ help = <<<EOF
143-
144- Usage: openapi [--option value] [/path/to/project ...]
145-
146- Options:
147- --config (-c) Generator config.
148- ex: -c operationId.hash=false
149- --defaults (-D) Show default config.
150- --output (-o) Path to store the generated documentation.
151- ex: --output openapi.yaml
152- --exclude (-e) Exclude path(s).
153- ex: --exclude vendor,library/Zend
154- --pattern (-n) Pattern of files to scan.
155- ex: --pattern "*.php" or --pattern "/\.(phps|php)$/"
156- --bootstrap (-b) Bootstrap php file(s) for defining constants, etc.
157- ex: --bootstrap config/constants.php
158- --add-processor (-a) Register an additional processor (allows multiple).
159- --remove-processor (-r) Remove an existing processor (allows multiple).
160- --format (-f) Force yaml or json.
161- --debug (-d) Show additional error information.
162- --version The OpenAPI version; defaults to {$ defaultVersion }.
163- --help (-h) Display this help message.
16428
165-
166- EOF ;
167- $ logger ->info ($ help );
168- exit (1 );
169- }
170-
171- $ errorTypes = [
172- E_ERROR => 'Error ' ,
173- E_WARNING => 'Warning ' ,
174- E_PARSE => 'Parser error ' ,
175- E_NOTICE => 'Notice ' ,
176- E_DEPRECATED => 'Deprecated ' ,
177- E_CORE_ERROR => 'Error(Core) ' ,
178- E_CORE_WARNING => 'Warning(Core) ' ,
179- E_COMPILE_ERROR => 'Error(compile) ' ,
180- E_COMPILE_WARNING => 'Warning(Compile) ' ,
181- E_RECOVERABLE_ERROR => 'Error(Recoverable) ' ,
182- E_USER_ERROR => 'Error ' ,
183- E_USER_WARNING => 'Warning ' ,
184- E_USER_NOTICE => 'Notice ' ,
185- E_USER_DEPRECATED => 'Deprecated ' ,
186- ];
187- set_error_handler (function ($ errno , $ errstr , $ file , $ line ) use ($ errorTypes , $ options , $ logger ) {
188- if (!(error_reporting () & $ errno )) {
189- // This error code is not included in error_reporting
190- return ;
191- }
192- $ type = array_key_exists ($ errno , $ errorTypes ) ? $ errorTypes [$ errno ] : 'Error ' ;
193- if ($ type === 'Deprecated ' ) {
194- $ logger ->info ($ errstr , ['prefix ' => $ type . ': ' ]);
195- } else {
196- $ logger ->error ($ errstr , ['prefix ' => $ type . ': ' ]);
197- }
198-
199- if ($ options ['debug ' ]) {
200- $ logger ->info (' in ' .$ file .' on line ' .$ line );
201- }
202- if (substr ($ type , 0 , 5 ) === 'Error ' ) {
203- exit ($ errno );
29+ return parent ::hasParameterOption ($ values , $ onlyParams );
20430 }
205- });
206-
207- set_exception_handler (function ($ exception ) use ($ logger ) {
208- $ logger ->error ($ exception );
209- exit ($ exception ->getCode () ?: 1 );
210- });
31+ };
32+ $ output = new ConsoleOutput ();
33+ $ logger = new ConsoleLogger ($ output );
34+ $ app = new Application ();
21135
212- $ exclude = null ;
213- if ($ options ['exclude ' ]) {
214- $ exclude = $ options ['exclude ' ];
215- if (strpos ($ exclude [0 ], ', ' ) !== false ) {
216- $ exploded = explode (', ' , $ exclude [0 ]);
217- $ logger ->error ('Comma-separated exclude paths are deprecated, use multiple --exclude statements: --exclude ' .$ exploded [0 ].' --exclude ' .$ exploded [1 ]);
218- $ exclude [0 ] = array_shift ($ exploded );
219- $ exclude = array_merge ($ exclude , $ exploded );
220- }
221- }
36+ // Remove Symfony's built-in options that conflict with our command options:
37+ // --version (-V): conflicts with our --version (VALUE_REQUIRED for OpenAPI spec version)
38+ // --no-interaction (-n): conflicts with our --pattern (-n)
39+ $ definition = $ app ->getDefinition ();
40+ $ options = $ definition ->getOptions ();
41+ unset($ options ['version ' ], $ options ['no-interaction ' ]);
42+ $ definition ->setOptions ($ options );
22243
223- $ pattern = "*.php " ;
224- if ($ options ['pattern ' ]) {
225- $ pattern = $ options ['pattern ' ];
226- }
227-
228- $ generator = new Generator ($ logger );
229- foreach ($ options ["add-processor " ] as $ processor ) {
230- $ class = '\OpenApi\Processors \\' .ucfirst ($ processor );
231- if (class_exists ($ class )) {
232- $ processor = new $ class ();
233- } elseif (class_exists ($ processor )) {
234- $ processor = new $ processor ();
235- }
236- $ generator ->getProcessorPipeline ()->add ($ processor );
237- }
238- foreach ($ options ["remove-processor " ] as $ processor ) {
239- $ class = class_exists ($ processor )
240- ? $ class
241- : '\OpenApi\Processors \\' .ucfirst ($ processor );
242- $ generator ->getProcessorPipeline ()->remove ($ class );
243- }
244-
245- $ analyser = new ReflectionAnalyser ([
246- new AttributeAnnotationFactory (),
247- new DocBlockAnnotationFactory (),
248- ]);
249- $ analyser ->setGenerator ($ generator );
250-
251- $ openapi = $ generator
252- ->setVersion ($ options ['version ' ])
253- ->setConfig ($ options ['config ' ])
254- ->setAnalyser ($ analyser )
255- ->generate (new SourceFinder ($ paths , $ exclude , $ pattern ));
256-
257- if ($ options ['output ' ] === false ) {
258- if (strtolower ($ options ['format ' ]) === 'json ' ) {
259- echo $ openapi ->toJson ();
260- } else {
261- echo $ openapi ->toYaml ();
262- }
263- echo "\n" ;
264- } else {
265- if (is_dir ($ options ['output ' ])) {
266- $ options ['output ' ] .= '/openapi.yaml ' ;
267- }
268- $ openapi ->saveAs ($ options ['output ' ], $ options ['format ' ]);
269- }
270- exit ($ logger ->loggedMessageAboveNotice () ? 1 : 0 );
44+ $ app ->addCommand (new GenerateCommand ($ logger ));
45+ $ app ->setDefaultCommand ('openapi ' , true );
46+ $ app ->run ($ input , $ output );
0 commit comments