Completed
Push — develop ( 3bf98d...ad187c )
by Jaap
06:26
created

RunCommand::execute()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 8
nc 1
nop 2
dl 0
loc 13
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * This file is part of phpDocumentor.
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 *
8
 * @copyright 2010-2015 Mike van Riel<[email protected]>
9
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
10
 * @link      http://phpdoc.org
11
 */
12
13
namespace phpDocumentor\Application\Console\Command;
14
15
use League\Event\Emitter;
16
use League\Tactician\CommandBus;
17
use phpDocumentor\Application\Configuration\ConfigurationFactory;
18
use phpDocumentor\DomainModel\Parse;
19
use phpDocumentor\DomainModel\Dsn;
20
use phpDocumentor\DomainModel\Parser\Documentation;
21
use phpDocumentor\DomainModel\Parser\DocumentationRepository;
22
use phpDocumentor\Infrastructure\FileSystemFactory;
23
use phpDocumentor\Infrastructure\Parser\Documentation\Api\FlySystemDefinition;
24
use phpDocumentor\DomainModel\Parser\ApiFileParsed;
25
use phpDocumentor\DomainModel\Parser\ApiParsingStarted;
26
use phpDocumentor\DomainModel\ConfigureCache;
27
use phpDocumentor\DomainModel\MergeConfigurationWithCommandLineOptions;
28
use phpDocumentor\DomainModel\Render;
29
use phpDocumentor\DomainModel\Parser\Version\DefinitionRepository;
30
use phpDocumentor\DomainModel\Renderer\RenderActionCompleted;
31
use phpDocumentor\DomainModel\Renderer\RenderingFinished;
32
use phpDocumentor\DomainModel\Renderer\RenderingStarted;
33
use Stash\Driver\FileSystem;
34
use Symfony\Component\Console\Command\Command;
35
use Symfony\Component\Console\Helper\HelperInterface;
36
use Symfony\Component\Console\Helper\ProgressBar;
37
use Symfony\Component\Console\Input\ArrayInput;
38
use Symfony\Component\Console\Input\InputArgument;
39
use Symfony\Component\Console\Input\InputInterface;
40
use Symfony\Component\Console\Input\InputOption;
41
use Symfony\Component\Console\Output\OutputInterface;
42
43
/**
44
 * Parse and transform the given directory (-d|-f) to the given location (-t).
45
 *
46
 * phpDocumentor creates documentation from PHP source files. The simplest way
47
 * to use it is:
48
 *
49
 *     $ phpdoc run -d <directory to parse> -t <output directory>
50
 *
51
 * This will parse every file ending with .php, .php3 and .phtml in <directory
52
 * to parse> and then output a HTML site containing easily readable documentation
53
 * in <output directory>.
54
 *
55
 * phpDocumentor will try to look for a phpdoc.dist.xml or phpdoc.xml file in your
56
 * current working directory and use that to override the default settings if
57
 * present. In the configuration file can you specify the same settings (and
58
 * more) as the command line provides.
59
 */
60
final class RunCommand extends Command
0 ignored issues
show
Complexity introduced by
The class RunCommand has a coupling between objects value of 26. Consider to reduce the number of dependencies under 13.
Loading history...
61
{
62
    /** @var CommandBus */
63
    private $commandBus;
64
65
    /** @var Emitter */
66
    private $emitter;
67
68
    /** @var DefinitionRepository */
69
    private $definitionRepository;
70
71
    /** @var DocumentationRepository */
72
    private $documentationRepository;
73
74
    /** @var ConfigurationFactory */
75
    private $configurationFactory;
76
77
    /** @var FileSystemFactory */
78
    private $fileSystemFactory;
79
80
    /**
81
     * Initializes the command with all necessary dependencies
82
     *
83
     * @param DefinitionRepository $definitionRepository
84
     * @param DocumentationRepository $documentationRepository
85
     * @param CommandBus $commandBus
86
     * @param Emitter $emitter
87
     * @param ConfigurationFactory $configurationFactory
88
     * @param FileSystemFactory $fileSystemFactory
89
     */
90
    public function __construct(
91
        DefinitionRepository $definitionRepository,
92
        DocumentationRepository $documentationRepository,
0 ignored issues
show
Comprehensibility Naming introduced by
The variable name $documentationRepository exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
93
        CommandBus $commandBus,
94
        Emitter $emitter,
95
        ConfigurationFactory $configurationFactory,
96
        FileSystemFactory $fileSystemFactory
97
    ) {
98
        $this->commandBus                   = $commandBus;
99
        $this->emitter                      = $emitter;
100
        $this->definitionRepository         = $definitionRepository;
101
        $this->documentationRepository      = $documentationRepository;
102
        $this->configurationFactory         = $configurationFactory;
103
        $this->fileSystemFactory            = $fileSystemFactory;
104
105
        parent::__construct('project:run');
106
    }
107
108
    /**
109
     * Initializes this command and sets the name, description, options and
110
     * arguments.
111
     *
112
     * @return void
113
     */
114
    protected function configure()
115
    {
116
        $this
117
            ->setAliases(array('run'))
118
            ->setDescription(
119
                'Parses and transforms the given files to a specified location'
120
            )
121
            ->setHelp(
122
                <<<HELP
123
phpDocumentor creates documentation from PHP source files. The simplest way
124
to use it is:
125
126
    <info>$ phpdoc run -d [directory to parse] -t [output directory]</info>
127
128
This will parse every file ending with .php, .php3 and .phtml in <directory
129
to parse> and then output a HTML site containing easily readable documentation
130
in <output directory>.
131
132
phpDocumentor will try to look for a phpdoc.dist.xml or phpdoc.xml file in your
133
current working directory and use that to override the default settings if
134
present. In the configuration file can you specify the same settings (and
135
more) as the command line provides.
136
137
<comment>Other commands</comment>
138
In addition to this command phpDocumentor also supports additional commands:
139
140
<comment>Available commands:</comment>
141
<info>  help
142
  list
143
  parse
144
  run
145
  transform
146
<comment>project</comment>
147
  project:parse
148
  project:run
149
  project:transform
150
<comment>template</comment>
151
  template:generate
152
  template:list
153
  template:package</info>
154
155
You can get a more detailed listing of the commands using the <info>list</info>
156
command and get help by prepending the word <info>help</info> to the command
157
name.
158
HELP
159
            )
160
            ->addOption(
161
                'target',
162
                't',
163
                InputOption::VALUE_OPTIONAL,
164
                'Path where to store the generated output'
165
            )
166
            ->addOption(
167
                'cache-folder',
168
                null,
169
                InputOption::VALUE_OPTIONAL,
170
                'Path where to store the cache files'
171
            )
172
            ->addOption(
173
                'filename',
174
                'f',
175
                InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY,
176
                'Comma-separated list of files to parse. The wildcards ? and * are supported'
177
            )
178
            ->addOption(
179
                'directory',
180
                'd',
181
                InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY,
182
                'Comma-separated list of directories to (recursively) parse'
183
            )
184
            ->addOption(
185
                'encoding',
186
                null,
187
                InputOption::VALUE_OPTIONAL,
188
                'encoding to be used to interpret source files with'
189
            )
190
            ->addOption(
191
                'extensions',
192
                'e',
193
                InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY,
194
                'Comma-separated list of extensions to parse, defaults to php, php3 and phtml'
195
            )
196
            ->addOption(
197
                'ignore',
198
                'i',
199
                InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY,
200
                'Comma-separated list of file(s) and directories (relative to the source-code directory) that will be '
201
                . 'ignored. Wildcards * and ? are supported'
202
            )
203
            ->addOption(
204
                'markers',
205
                'm',
206
                InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY,
207
                'Comma-separated list of markers/tags to filter'
208
            )
209
            ->addOption(
210
                'title',
211
                null,
212
                InputOption::VALUE_OPTIONAL,
213
                'Sets the title for this project; default is the phpDocumentor logo'
214
            )
215
            ->addOption(
216
                'force',
217
                null,
218
                InputOption::VALUE_NONE,
219
                'Forces a full build of the documentation, does not increment existing documentation'
220
            )
221
            ->addOption(
222
                'visibility',
223
                null,
224
                InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY,
225
                'Specifies the parse visibility that should be displayed in the documentation (comma separated e.g. '
226
                . '"public,protected")'
227
            )
228
            ->addOption(
229
                'defaultpackagename',
230
                null,
231
                InputOption::VALUE_OPTIONAL,
232
                'Name to use for the default package.'
233
            )
234
            ->addOption(
235
                'sourcecode',
236
                null,
237
                InputOption::VALUE_NONE,
238
                'Whether to include syntax highlighted source code'
239
            )
240
            ->addOption(
241
                'progressbar',
242
                'p',
243
                InputOption::VALUE_NONE,
244
                'Whether to show a progress bar; will automatically quiet logging to stdout'
245
            )
246
            ->addOption(
247
                'template',
248
                null,
249
                InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY,
250
                'Name of the template to use (optional)'
251
            )
252
            ->addOption(
253
                'parseprivate',
254
                null,
255
                InputOption::VALUE_NONE,
256
                'Whether to parse DocBlocks marked with @internal tag'
257
            )
258
            ->addArgument(
259
                'paths',
260
                InputArgument::OPTIONAL | InputArgument::IS_ARRAY,
261
                'One or more files and folders to process',
262
                array()
263
            );
264
265
        parent::configure();
266
    }
267
268
    /**
269
     * Executes the business logic involved with this command.
270
     *
271
     * @param InputInterface $input
272
     * @param OutputInterface $output
273
     *
274
     * @return int
275
     */
276
    protected function execute(InputInterface $input, OutputInterface $output)
277
    {
278
        $this->outputHeader($output);
279
        $this->attachListeners($input, $output);
280
281
        $this->mergeArgumentsIntoConfiguration($input);
282
        $destination = $this->getDestination();
283
284
        $this->configureCache();
285
        $this->renderDocumentationTo($destination);
286
287
        $this->outputFooter($output, $destination);
288
    }
289
290
    /**
291
     * Connect a series of output messages to various events to display progress.
292
     *
293
     * @param InputInterface  $input
294
     * @param OutputInterface $output
295
     *
296
     * @return void
297
     */
298
    private function attachListeners(InputInterface $input, OutputInterface $output)
0 ignored issues
show
Unused Code introduced by
The parameter $input is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
299
    {
300
        $this->emitter->addListener(
301
            ApiParsingStarted::class,
302
            function (ApiParsingStarted $event) use ($output) {
303
                /** @var FlySystemDefinition $definition */
304
                $definition = $event->definition();
305
                $output->writeln(
306
                    sprintf('Parsing <info>%d</info> files', count($definition->getFiles()))
307
                );
308
            }
309
        );
310
        $this->emitter->addListener(
311
            ApiFileParsed::class,
312
            function (ApiFileParsed $event) use ($output) {
313
                $output->writeln(sprintf('  Parsed <info>%s</info>', (string)$event->filename()));
314
            }
315
        );
316
        $this->emitter->addListener(
317
            RenderingStarted::class,
318
            function (RenderingStarted $event) use ($output) {
0 ignored issues
show
Unused Code introduced by
The parameter $event is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
319
                $output->writeln('Started rendering');
320
            }
321
        );
322
        $this->emitter->addListener(
323
            RenderingFinished::class,
324
            function (RenderingFinished $event) use ($output) {
0 ignored issues
show
Unused Code introduced by
The parameter $event is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
325
                $output->writeln('Completed rendering');
326
            }
327
        );
328
        $this->emitter->addListener(
329
            RenderActionCompleted::class,
330
            function (RenderActionCompleted $event) use ($output) {
331
                $output->writeln(sprintf('  %s', (string)$event->action()));
332
            }
333
        );
334
    }
335
336
    /**
337
     * @return Dsn
338
     */
339
    private function getDestination()
340
    {
341
        $config = $this->configurationFactory->get();
342
343
        return $config['phpdocumentor']['paths']['output'];
344
    }
345
346
    /**
347
     * Returns the destination as a string and expands it to show the absolute path if the destination is on disk.
348
     *
349
     * Because the destination location is created during rendering we can only expand the path using realpath
350
     * because realpath will return false on a non-existent location.
351
     *
352
     * @param Dsn $destination
353
     *
354
     * @return string
355
     */
356
    private function getExpandedDestination($destination)
357
    {
358
        return $destination->getScheme() == 'file'
359
            ? realpath($destination->getPath())
360
            : $destination;
361
    }
362
363
    private function getTemplates()
364
    {
365
        $config = $this->configurationFactory->get();
366
367
        return $config['phpdocumentor']['templates'];
368
    }
369
370
    private function configureCache()
371
    {
372
        $config = $this->configurationFactory->get();
373
374
        $cacheLocation = $config['phpdocumentor']['paths']['cache'];
375
        $cacheEnabled = $config['phpdocumentor']['use-cache'];
376
377
        $this->commandBus->handle(new ConfigureCache($cacheLocation, $cacheEnabled));
378
    }
379
380
    /**
381
     * @param OutputInterface $output
382
     */
383
    protected function outputHeader(OutputInterface $output)
384
    {
385
        $output->writeln(
386
            sprintf(
387
                '<info>%s</info> version <comment>%s</comment>' . PHP_EOL,
388
                $this->getApplication()->getName(),
389
                $this->getApplication()->getVersion()
390
            )
391
        );
392
    }
393
394
    private function mergeArgumentsIntoConfiguration(InputInterface $input)
395
    {
396
        $this->commandBus->handle(
397
            new MergeConfigurationWithCommandLineOptions($input->getOptions(), $input->getArguments())
398
        );
399
    }
400
401
    private function renderDocumentationTo($destination)
402
    {
403
        $destinationFilesystem = $this->fileSystemFactory->create($destination);
0 ignored issues
show
Comprehensibility Naming introduced by
The variable name $destinationFilesystem exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
404
        $templates = $this->getTemplates();
405
406
        foreach ($this->definitionRepository->fetchAll() as $definition) {
407
            // TODO: Does this mean that once cached it is never refreshed?
408
            if (!$this->documentationRepository->hasForVersionNumber($definition->getVersionNumber())) {
0 ignored issues
show
Documentation introduced by
$definition->getVersionNumber() is of type integer|double, but the function expects a object<phpDocumentor\Dom...\Parser\Version\Number>.

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);
Loading history...
409
                $this->commandBus->handle(new Parse($definition));
410
            }
411
412
            $documentation = $this->documentationRepository->findByVersionNumber($definition->getVersionNumber());
0 ignored issues
show
Documentation introduced by
$definition->getVersionNumber() is of type integer|double, but the function expects a object<phpDocumentor\Dom...\Parser\Version\Number>.

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);
Loading history...
413
414
            $this->commandBus->handle(new Render($documentation, $destinationFilesystem, $templates));
0 ignored issues
show
Bug introduced by
It seems like $documentation defined by $this->documentationRepo...on->getVersionNumber()) on line 412 can be null; however, phpDocumentor\DomainModel\Render::__construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
415
        }
416
    }
417
418
    private function outputFooter(OutputInterface $output, $destination)
419
    {
420
        $expandedDestination = $this->getExpandedDestination($destination);
421
        $output->writeln(sprintf(PHP_EOL . '<fg=black;bg=green>OK (%s)</>', $expandedDestination));
422
    }
423
}
424