These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | /** |
||
4 | * This file is part of the ApiGen (http://apigen.org) |
||
5 | * |
||
6 | * For the full copyright and license information, please view |
||
7 | * the file LICENSE that was distributed with this source code. |
||
8 | */ |
||
9 | |||
10 | namespace ApiGen\Console\Command; |
||
11 | |||
12 | use ApiGen\Configuration\Configuration; |
||
13 | use ApiGen\Configuration\Readers\ReaderFactory; |
||
14 | use ApiGen\Contracts\Console\IO\IOInterface; |
||
15 | use ApiGen\Contracts\Generator\GeneratorQueueInterface; |
||
16 | use ApiGen\Contracts\Parser\ParserInterface; |
||
17 | use ApiGen\Contracts\Parser\ParserStorageInterface; |
||
18 | use ApiGen\Theme\ThemeResources; |
||
19 | use ApiGen\Utils\FileSystem; |
||
20 | use ApiGen\Utils\Finder\FinderInterface; |
||
21 | use Symfony\Component\Console\Input\InputInterface; |
||
22 | use Symfony\Component\Console\Input\InputOption; |
||
23 | use Symfony\Component\Console\Output\OutputInterface; |
||
24 | use TokenReflection\Exception\FileProcessingException; |
||
25 | |||
26 | class GenerateCommand extends AbstractCommand |
||
27 | { |
||
28 | |||
29 | /** |
||
30 | * @var Configuration |
||
31 | */ |
||
32 | private $configuration; |
||
33 | |||
34 | /** |
||
35 | * @var ParserInterface |
||
36 | */ |
||
37 | private $parser; |
||
38 | |||
39 | /** |
||
40 | * @var ParserStorageInterface |
||
41 | */ |
||
42 | private $parserResult; |
||
43 | |||
44 | /** |
||
45 | * @var GeneratorQueueInterface |
||
46 | */ |
||
47 | private $generatorQueue; |
||
48 | |||
49 | /** |
||
50 | * @var FileSystem |
||
51 | */ |
||
52 | private $fileSystem; |
||
53 | |||
54 | /** |
||
55 | * @var ThemeResources |
||
56 | */ |
||
57 | private $themeResources; |
||
58 | |||
59 | /** |
||
60 | * @var IOInterface |
||
61 | */ |
||
62 | private $io; |
||
63 | |||
64 | /** |
||
65 | * @var FinderInterface |
||
66 | */ |
||
67 | private $finder; |
||
68 | |||
69 | |||
70 | public function __construct( |
||
71 | Configuration $configuration, |
||
72 | ParserInterface $parser, |
||
73 | ParserStorageInterface $parserResult, |
||
74 | GeneratorQueueInterface $generatorQueue, |
||
75 | FileSystem $fileSystem, |
||
76 | ThemeResources $themeResources, |
||
77 | IOInterface $io, |
||
78 | FinderInterface $finder |
||
79 | ) { |
||
80 | parent::__construct(); |
||
81 | $this->configuration = $configuration; |
||
82 | $this->parser = $parser; |
||
83 | $this->parserResult = $parserResult; |
||
84 | $this->generatorQueue = $generatorQueue; |
||
85 | $this->fileSystem = $fileSystem; |
||
86 | $this->themeResources = $themeResources; |
||
87 | $this->io = $io; |
||
88 | $this->finder = $finder; |
||
89 | } |
||
90 | |||
91 | |||
92 | protected function configure() |
||
93 | { |
||
94 | $this->setName('generate') |
||
95 | ->setDescription('Generate API documentation') |
||
96 | ->addOption( |
||
97 | 'source', |
||
98 | 's', |
||
99 | InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, |
||
100 | 'Dirs or files documentation is generated for.' |
||
101 | ) |
||
102 | ->addOption('destination', 'd', InputOption::VALUE_REQUIRED, 'Target dir for documentation.') |
||
103 | ->addOption( |
||
104 | 'accessLevels', |
||
105 | null, |
||
106 | InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, |
||
107 | 'Access levels of included method and properties [options: public, protected, private].', |
||
108 | ['public', 'protected'] |
||
109 | ) |
||
110 | ->addOption( |
||
111 | 'annotationGroups', |
||
112 | null, |
||
113 | InputOption::VALUE_REQUIRED, |
||
114 | 'Generate page with elements with specific annotation.' |
||
115 | ) |
||
116 | ->addOption( |
||
117 | 'config', |
||
118 | null, |
||
119 | InputOption::VALUE_REQUIRED, |
||
120 | 'Custom path to apigen.neon config file.', |
||
121 | getcwd() . '/apigen.neon' |
||
122 | ) |
||
123 | ->addOption( |
||
124 | 'googleCseId', |
||
125 | null, |
||
126 | InputOption::VALUE_REQUIRED, |
||
127 | 'Custom google search engine id (for search box).' |
||
128 | ) |
||
129 | ->addOption( |
||
130 | 'baseUrl', |
||
131 | null, |
||
132 | InputOption::VALUE_REQUIRED, |
||
133 | 'Base url used for sitemap (for search box).' |
||
134 | ) |
||
135 | ->addOption('googleAnalytics', null, InputOption::VALUE_REQUIRED, 'Google Analytics tracking code.') |
||
136 | ->addOption('debug', null, InputOption::VALUE_NONE, 'Turn on debug mode.') |
||
137 | ->addOption( |
||
138 | 'download', |
||
139 | null, |
||
140 | InputOption::VALUE_NONE, |
||
141 | 'Add link to ZIP archive of documentation.' |
||
142 | ) |
||
143 | ->addOption( |
||
144 | 'extensions', |
||
145 | null, |
||
146 | InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, |
||
147 | 'Scanned file extensions.', |
||
148 | ['php'] |
||
149 | ) |
||
150 | ->addOption( |
||
151 | 'exclude', |
||
152 | null, |
||
153 | InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, |
||
154 | 'Directories and files matching this mask will not be parsed (e.g. */tests/*).' |
||
155 | ) |
||
156 | ->addOption( |
||
157 | 'groups', |
||
158 | null, |
||
159 | InputOption::VALUE_REQUIRED, |
||
160 | 'The way elements are grouped in menu [options: namespaces, packages].', |
||
161 | 'namespaces' |
||
162 | ) |
||
163 | ->addOption( |
||
164 | 'main', |
||
165 | null, |
||
166 | InputOption::VALUE_REQUIRED, |
||
167 | 'Elements with this name prefix will be first in tree.' |
||
168 | ) |
||
169 | ->addOption('internal', null, InputOption::VALUE_NONE, 'Include elements marked as @internal.') |
||
170 | ->addOption('php', null, InputOption::VALUE_NONE, 'Generate documentation for PHP internal classes.') |
||
171 | ->addOption( |
||
172 | 'noSourceCode', |
||
173 | null, |
||
174 | InputOption::VALUE_NONE, |
||
175 | 'Do not generate highlighted source code for elements.' |
||
176 | ) |
||
177 | ->addOption('templateTheme', null, InputOption::VALUE_REQUIRED, 'ApiGen template theme name.', 'default') |
||
178 | ->addOption( |
||
179 | 'templateConfig', |
||
180 | null, |
||
181 | InputOption::VALUE_REQUIRED, |
||
182 | 'Your own template config, has higher priority then --template-theme.' |
||
183 | ) |
||
184 | ->addOption('title', null, InputOption::VALUE_REQUIRED, 'Title of generated documentation.') |
||
185 | ->addOption( |
||
186 | 'tree', |
||
187 | null, |
||
188 | InputOption::VALUE_NONE, |
||
189 | 'Generate tree view of classes, interfaces, traits and exceptions.' |
||
190 | ) |
||
191 | |||
192 | /** |
||
193 | * @deprecated since version 4.2, to be removed in 5.0 |
||
194 | */ |
||
195 | ->addOption( |
||
196 | 'deprecated', |
||
197 | null, |
||
198 | InputOption::VALUE_NONE, |
||
199 | 'Generate documentation for elements marked as @deprecated (deprecated, only present for BC).' |
||
200 | ) |
||
201 | ->addOption( |
||
202 | 'todo', |
||
203 | null, |
||
204 | InputOption::VALUE_NONE, |
||
205 | 'Generate documentation for elements marked as @todo (deprecated, only present for BC).' |
||
206 | ) |
||
207 | ->addOption( |
||
208 | 'charset', |
||
209 | null, |
||
210 | InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, |
||
211 | 'Charset of scanned files (deprecated, only present for BC).' |
||
212 | ) |
||
213 | ->addOption( |
||
214 | 'skip-doc-path', |
||
215 | null, |
||
216 | InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, |
||
217 | 'Files matching this mask will be included in class tree,' |
||
218 | . ' but will not create a link to their documentation (deprecated, only present for BC).' |
||
219 | ) |
||
220 | ->addOption( |
||
221 | 'overwrite', |
||
222 | 'o', |
||
223 | InputOption::VALUE_NONE, |
||
224 | 'Force overwrite destination directory' |
||
225 | ); |
||
226 | } |
||
227 | |||
228 | |||
229 | protected function execute(InputInterface $input, OutputInterface $output) |
||
230 | { |
||
231 | try { |
||
232 | $options = $this->prepareOptions($input->getOptions()); |
||
233 | $this->scanAndParse($options); |
||
234 | $this->generate($options); |
||
235 | return 0; |
||
236 | } catch (\Exception $e) { |
||
237 | $output->writeln( |
||
238 | sprintf(PHP_EOL . '<error>%s</error>', $e->getMessage()) |
||
239 | ); |
||
240 | return 1; |
||
241 | } |
||
242 | } |
||
243 | |||
244 | |||
245 | private function scanAndParse(array $options) |
||
246 | { |
||
247 | $this->io->writeln('<info>Scanning sources and parsing</info>'); |
||
248 | |||
249 | $files = $this->finder->find($options['source'], $options['exclude'], $options['extensions']); |
||
250 | $this->parser->parse($files); |
||
0 ignored issues
–
show
|
|||
251 | |||
252 | $this->reportParserErrors($this->parser->getErrors()); |
||
253 | |||
254 | $stats = $this->parserResult->getDocumentedStats(); |
||
255 | $this->io->writeln(sprintf( |
||
256 | 'Found <comment>%d classes</comment>, <comment>%d constants</comment> and <comment>%d functions</comment>', |
||
257 | $stats['classes'], |
||
258 | $stats['constants'], |
||
259 | $stats['functions'] |
||
260 | )); |
||
261 | } |
||
262 | |||
263 | |||
264 | private function generate(array $options) |
||
265 | { |
||
266 | $this->prepareDestination($options['destination'], $options['overwrite']); |
||
267 | $this->io->writeln('<info>Generating API documentation</info>'); |
||
268 | $this->generatorQueue->run(); |
||
269 | } |
||
270 | |||
271 | |||
272 | private function reportParserErrors(array $errors) |
||
273 | { |
||
274 | /** @var FileProcessingException[] $errors */ |
||
275 | foreach ($errors as $error) { |
||
276 | $output = null; |
||
277 | if ($this->configuration->getOption('debug')) { |
||
278 | $output = $error->getDetail(); |
||
279 | } else { |
||
280 | /** @var \Exception[] $reasons */ |
||
281 | $reasons = $error->getReasons(); |
||
282 | if (isset($reasons[0]) && count($reasons)) { |
||
283 | $output = $reasons[0]->getMessage(); |
||
284 | } |
||
285 | } |
||
286 | if ($output) { |
||
287 | $this->io->writeln(sprintf('<error>Parse error: "%s"</error>', $output)); |
||
288 | } |
||
289 | } |
||
290 | } |
||
291 | |||
292 | |||
293 | /** |
||
294 | * @return array |
||
295 | */ |
||
296 | private function prepareOptions(array $cliOptions) |
||
297 | { |
||
298 | $options = $this->convertDashKeysToCamel($cliOptions); |
||
299 | $options = $this->loadOptionsFromConfig($options); |
||
300 | |||
301 | $this->warnAboutDeprecatedOptions($options); |
||
0 ignored issues
–
show
The method
ApiGen\Console\Command\G...boutDeprecatedOptions() has been deprecated with message: since version 4.2, to be removed in 5.0
This method has been deprecated. The supplier of the class has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead. ![]() |
|||
302 | $options = $this->unsetDeprecatedOptions($options); |
||
0 ignored issues
–
show
The method
ApiGen\Console\Command\G...nsetDeprecatedOptions() has been deprecated with message: since version 4.2, to be removed in 5.0
This method has been deprecated. The supplier of the class has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead. ![]() |
|||
303 | |||
304 | return $this->configuration->resolveOptions($options); |
||
305 | } |
||
306 | |||
307 | |||
308 | /** |
||
309 | * @return array |
||
310 | */ |
||
311 | private function convertDashKeysToCamel(array $options) |
||
312 | { |
||
313 | foreach ($options as $key => $value) { |
||
314 | $camelKey = $this->camelFormat($key); |
||
315 | if ($key !== $camelKey) { |
||
316 | $options[$camelKey] = $value; |
||
317 | unset($options[$key]); |
||
318 | } |
||
319 | } |
||
320 | return $options; |
||
321 | } |
||
322 | |||
323 | |||
324 | /** |
||
325 | * @param string $name |
||
326 | * @return string |
||
327 | */ |
||
328 | private function camelFormat($name) |
||
329 | { |
||
330 | return preg_replace_callback('~-([a-z])~', function ($matches) { |
||
331 | return strtoupper($matches[1]); |
||
332 | }, $name); |
||
333 | } |
||
334 | |||
335 | |||
336 | /** |
||
337 | * @return array |
||
338 | */ |
||
339 | private function loadOptionsFromConfig(array $options) |
||
340 | { |
||
341 | $configFilePaths = [ |
||
342 | $options['config'], |
||
343 | getcwd() . '/apigen.neon', |
||
344 | getcwd() . '/apigen.yaml', |
||
345 | getcwd() . '/apigen.neon.dist', |
||
346 | getcwd() . '/apigen.yaml.dist' |
||
347 | ]; |
||
348 | |||
349 | foreach ($configFilePaths as $configFile) { |
||
350 | if (file_exists($configFile)) { |
||
351 | $configFileOptions = ReaderFactory::getReader($configFile)->read(); |
||
352 | $options = array_merge($options, $configFileOptions); |
||
353 | break; |
||
354 | } |
||
355 | } |
||
356 | return $options; |
||
357 | } |
||
358 | |||
359 | |||
360 | /** |
||
361 | * @param string $destination |
||
362 | */ |
||
363 | private function prepareDestination($destination, $allowOverwrite = false) |
||
364 | { |
||
365 | if (!$allowOverwrite) { |
||
366 | $this->cleanDestinationWithCaution($destination); |
||
367 | } |
||
368 | $this->themeResources->copyToDestination($destination); |
||
369 | } |
||
370 | |||
371 | |||
372 | /** |
||
373 | * @param string $destination |
||
374 | */ |
||
375 | private function cleanDestinationWithCaution($destination) |
||
376 | { |
||
377 | if (! $this->fileSystem->isDirEmpty($destination)) { |
||
378 | if ($this->io->ask('<warning>Destination is not empty. Do you want to erase it?</warning>', true)) { |
||
0 ignored issues
–
show
true is of type boolean , but the function expects a null|string .
It seems like the type of the argument is not accepted by the function/method which you are calling. In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug. We suggest to add an explicit type cast like in the following example: function acceptsInteger($int) { }
$x = '123'; // string "123"
// Instead of
acceptsInteger($x);
// we recommend to use
acceptsInteger((integer) $x);
![]() |
|||
379 | $this->fileSystem->purgeDir($destination); |
||
380 | } |
||
381 | } |
||
382 | } |
||
383 | |||
384 | |||
385 | /** |
||
386 | * @deprecated since version 4.2, to be removed in 5.0 |
||
387 | */ |
||
388 | private function warnAboutDeprecatedOptions(array $options) |
||
389 | { |
||
390 | if (isset($options['charset']) && $options['charset']) { |
||
391 | $this->io->writeln( |
||
392 | '<warning>You are using the deprecated option "charset". ' . |
||
393 | 'UTF-8 is default now.</warning>' |
||
394 | ); |
||
395 | } |
||
396 | |||
397 | if (isset($options['deprecated']) && $options['deprecated']) { |
||
398 | $this->io->writeln( |
||
399 | '<warning>You are using the deprecated option "deprecated". ' . |
||
400 | 'Use "--annotation-groups=deprecated" instead</warning>' |
||
401 | ); |
||
402 | } |
||
403 | |||
404 | if (isset($options['todo']) && $options['todo']) { |
||
405 | $this->io->writeln( |
||
406 | '<warning>You are using the deprecated option "todo". Use "--annotation-groups=todo" instead</warning>' |
||
407 | ); |
||
408 | } |
||
409 | |||
410 | if (isset($options['skipDocPath']) && $options['skipDocPath']) { |
||
411 | $this->io->writeln( |
||
412 | '<warning>You are using the deprecated option "skipDocPath". Use "exclude" instead.</warning>' |
||
413 | ); |
||
414 | } |
||
415 | } |
||
416 | |||
417 | |||
418 | /** |
||
419 | * @deprecated since version 4.2, to be removed in 5.0 |
||
420 | * |
||
421 | * @return array |
||
422 | */ |
||
423 | private function unsetDeprecatedOptions(array $options) |
||
424 | { |
||
425 | unset($options['charset'], $options['skipDocPath']); |
||
426 | return $options; |
||
427 | } |
||
428 | } |
||
429 |
It seems like the type of the argument is not accepted by the function/method which you are calling.
In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.
We suggest to add an explicit type cast like in the following example: