Completed
Push — develop ( 114abf...08add9 )
by Jaap
03:44
created

ParseCommand::getFileCollection()   B

Complexity

Conditions 7
Paths 12

Size

Total Lines 60
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 25
CRAP Score 8.3984

Importance

Changes 0
Metric Value
cc 7
eloc 34
nc 12
nop 1
dl 0
loc 60
rs 7.4661
c 0
b 0
f 0
ccs 25
cts 36
cp 0.6944
crap 8.3984

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * phpDocumentor
4
 *
5
 * PHP Version 5.3
6
 *
7
 * @copyright 2010-2014 Mike van Riel / Naenius (http://www.naenius.com)
8
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
9
 * @link      http://phpdoc.org
10
 */
11
12
namespace phpDocumentor\Parser\Command\Project;
13
14
use League\Flysystem\MountManager;
15
use phpDocumentor\Command\Command;
16
use phpDocumentor\Command\Helper\ConfigurationHelper;
17
use phpDocumentor\Descriptor\Cache\ProjectDescriptorMapper;
18
use phpDocumentor\Descriptor\Example\Finder;
19
use phpDocumentor\Descriptor\ProjectDescriptor;
20
use phpDocumentor\Descriptor\ProjectDescriptorBuilder;
21
use phpDocumentor\DomainModel\Dsn;
22
use phpDocumentor\Fileset\Collection;
23
use phpDocumentor\Infrastructure\FlySystemFactory;
24
use phpDocumentor\Infrastructure\Parser\FlySystemFile;
25
use phpDocumentor\Infrastructure\Parser\SpecificationFactory;
26
use phpDocumentor\Parser\Event\PreFileEvent;
27
use phpDocumentor\Parser\Exception\FilesNotFoundException;
28
use phpDocumentor\Parser\Parser;
29
use phpDocumentor\Parser\Util\ParserPopulator;
30
use phpDocumentor\Partials\Collection as PartialsCollection;
31
use phpDocumentor\Reflection\DocBlock\ExampleFinder;
32
use phpDocumentor\Reflection\File;
33
use Symfony\Component\Console\Helper\ProgressHelper;
34
use Symfony\Component\Console\Input\InputInterface;
35
use Symfony\Component\Console\Input\InputOption;
36
use Symfony\Component\Console\Output\OutputInterface;
37
use Symfony\Component\Filesystem\Filesystem;
38
use Zend\Cache\Storage\StorageInterface;
39
use Zend\I18n\Translator\Translator;
40
41
/**
42
 * Parses the given source code and creates a structure file.
43
 *
44
 * The parse task uses the source files defined either by -f or -d options and
45
 * generates a structure file (structure.xml) at the target location (which is
46
 * the folder 'output' unless the option -t is provided).
47
 */
48
class ParseCommand extends Command
49
{
50
    /** @var ProjectDescriptorBuilder $builder */
51
    protected $builder;
52
53
    /** @var Parser $parser */
54
    protected $parser;
55
56
    /** @var Translator */
57
    protected $translator;
58
59
    /** @var StorageInterface */
60
    private $cache;
61
62
    /**
63
     * @var ExampleFinder
64
     */
65
    private $exampleFinder;
66
    /**
67
     * @var PartialsCollection
68
     */
69
    private $partials;
70
71
    /**
72
     * ParseCommand constructor.
73
     * @param ProjectDescriptorBuilder $builder
74
     * @param Parser $parser
75
     * @param Translator $translator
76
     * @param StorageInterface $cache
77
     * @param ExampleFinder $exampleFinder
78
     * @param PartialsCollection $partials
79
     */
80
    public function __construct(
81
        ProjectDescriptorBuilder $builder,
82
        Parser $parser,
83
        Translator $translator,
84
        StorageInterface $cache,
85
        ExampleFinder $exampleFinder,
86
        PartialsCollection $partials
87
    )
88
    {
0 ignored issues
show
Coding Style introduced by
The closing parenthesis and the opening brace of a multi-line function declaration must be on the same line
Loading history...
89
        $this->builder = $builder;
90
        $this->parser = $parser;
91
        $this->translator = $translator;
92
        $this->cache = $cache;
93
        $this->exampleFinder = $exampleFinder;
94
        $this->partials = $partials;
95
96
        parent::__construct('project:parse');
97
    }
98
99
    /**
100
     * @return ProjectDescriptorBuilder
101
     */
102
    public function getBuilder(): ProjectDescriptorBuilder
103
    {
104
        return $this->builder;
105
    }
106
107
    /**
108
     * @return \phpDocumentor\Parser\Parser
109
     */
110
    public function getParser(): Parser
111
    {
112
        return $this->parser;
113
    }
114
115
    /**
116
     * Returns the Cache.
117 1
     *
118
     * @return StorageInterface
119 1
     * @throws \InvalidArgumentException
120
     */
121
    protected function getCache(): StorageInterface
122
    {
123
        return $this->cache;
124
    }
125
126
    /**
127
     * Initializes this command and sets the name, description, options and
128 1
     * arguments.
129
     *
130
     * @return void
131 1
     */
132
    protected function configure()
133 1
    {
134 1
        // minimization of the following expression
135 1
        $VALUE_OPTIONAL_ARRAY = InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY;
136 1
137 1
        $this->setAliases(array('parse'))
138 1
            ->setDescription($this->__('PPCPP-DESCRIPTION'))
139 1
            ->setHelp($this->__('PPCPP-HELPTEXT'))
140 1
            ->addOption('filename', 'f', $VALUE_OPTIONAL_ARRAY, $this->__('PPCPP:OPT-FILENAME'))
141 1
            ->addOption('directory', 'd', $VALUE_OPTIONAL_ARRAY, $this->__('PPCPP:OPT-DIRECTORY'))
142 1
            ->addOption('target', 't', InputOption::VALUE_OPTIONAL, $this->__('PPCPP:OPT-TARGET'))
143 1
            ->addOption('encoding', null, InputOption::VALUE_OPTIONAL, $this->__('PPCPP:OPT-ENCODING'))
144 1
            ->addOption('extensions', 'e', $VALUE_OPTIONAL_ARRAY, $this->__('PPCPP:OPT-EXTENSIONS'))
145 1
            ->addOption('ignore', 'i', $VALUE_OPTIONAL_ARRAY, $this->__('PPCPP:OPT-IGNORE'))
146 1
            ->addOption('ignore-tags', null, $VALUE_OPTIONAL_ARRAY, $this->__('PPCPP:OPT-IGNORETAGS'))
147 1
            ->addOption('hidden', null, InputOption::VALUE_NONE, $this->__('PPCPP:OPT-HIDDEN'))
148 1
            ->addOption('ignore-symlinks', null, InputOption::VALUE_NONE, $this->__('PPCPP:OPT-IGNORESYMLINKS'))
149 1
            ->addOption('markers', 'm', $VALUE_OPTIONAL_ARRAY, $this->__('PPCPP:OPT-MARKERS'), array('TODO', 'FIXME'))
150 1
            ->addOption('title', null, InputOption::VALUE_OPTIONAL, $this->__('PPCPP:OPT-TITLE'))
151 1
            ->addOption('force', null, InputOption::VALUE_NONE, $this->__('PPCPP:OPT-FORCE'))
152 1
            ->addOption('validate', null, InputOption::VALUE_NONE, $this->__('PPCPP:OPT-VALIDATE'))
153 1
            ->addOption('visibility', null, $VALUE_OPTIONAL_ARRAY, $this->__('PPCPP:OPT-VISIBILITY'))
154 1
            ->addOption('sourcecode', null, InputOption::VALUE_NONE, $this->__('PPCPP:OPT-SOURCECODE'))
155 1
            ->addOption('progressbar', 'p', InputOption::VALUE_NONE, $this->__('PPCPP:OPT-PROGRESSBAR'))
156 1
            ->addOption('parseprivate', null, InputOption::VALUE_NONE, 'PPCPP:OPT-PARSEPRIVATE')
157 1
            ->addOption(
158 1
                'defaultpackagename',
159
                null,
160
                InputOption::VALUE_OPTIONAL,
161 1
                $this->__('PPCPP:OPT-DEFAULTPACKAGENAME'),
162 1
                'Default'
163
            );
164
165
        parent::configure();
166
    }
167
168
    /**
169
     * Executes the business logic involved with this command.
170
     *
171
     * @param InputInterface $input
172
     * @param OutputInterface $output
173
     *
174 1
     * @throws \Exception if the target location is not a folder.
175
     *
176
     * @return integer
177 1
     */
178 1
    protected function execute(InputInterface $input, OutputInterface $output): int
179 1
    {
180
        /** @var ConfigurationHelper $configurationHelper */
181
        $configurationHelper = $this->getHelper('phpdocumentor_configuration');
182
        $target = $configurationHelper->getOption($input, 'target', 'parser/target');
183 1
        if (strpos($target, '/tmp/') === 0) {
184 1
            $target = str_replace('/tmp/', sys_get_temp_dir() . DIRECTORY_SEPARATOR, $target);
185 1
        }
186
187 1
        $fileSystem = new Filesystem();
188
        if (!$fileSystem->isAbsolutePath($target)) {
189
            $target = getcwd() . DIRECTORY_SEPARATOR . $target;
190 1
        }
191
        if (!file_exists($target)) {
192
            mkdir($target, 0777, true);
193 1
        }
194
        if (!is_dir($target)) {
195 1
            throw new \Exception($this->__('PPCPP:EXC-BADTARGET'));
196 1
        }
197 1
        $this->getCache()->getOptions()->setCacheDir($target);
198
199 1
        $builder = $this->getBuilder();
200 1
        $builder->createProjectDescriptor();
201
        $projectDescriptor = $builder->getProjectDescriptor();
202 1
203 1
        $output->write($this->__('PPCPP:LOG-COLLECTING'));
204 1
        $files = $this->getFileCollection($input);
205
206
        //TODO: Should determine root based on filesystems. Could be an issue for multiple. Need some config update here.
207 1
        $this->exampleFinder->setSourceDirectory(getcwd());
208 1
        $this->exampleFinder->setExampleDirectories($configurationHelper->getConfigValueFromPath('files/examples'));
209 1
        $output->writeln($this->__('PPCPP:LOG-OK'));
210
211
        /** @var ProgressHelper $progress */
212 1
        $progress = $this->getProgressBar($input);
213 1
        if (!$progress) {
214
            $this->getHelper('phpdocumentor_logger')->connectOutputToLogging($output, $this);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Symfony\Component\Console\Helper\HelperInterface as the method connectOutputToLogging() does only exist in the following implementations of said interface: phpDocumentor\Command\Helper\LoggerHelper.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
215 1
        }
216
217
        $output->write($this->__('PPCPP:LOG-INITIALIZING'));
218
        $this->populateParser($input);
219
220 1
        if ($progress) {
221 1
            $progress->start($output, \count($files));
222
        }
223 1
224 1
        try {
225 1
            $output->writeln($this->__('PPCPP:LOG-OK'));
226
            $output->writeln($this->__('PPCPP:LOG-PARSING'));
227 1
228 1
            $mapper = new ProjectDescriptorMapper($this->getCache());
229 1
            //TODO: Re-enable garbage collection here.
230 1
            //$mapper->garbageCollect($files);
231
            $mapper->populate($projectDescriptor);
232 1
233 1
            $visibility = (array)$configurationHelper->getOption($input, 'visibility', 'parser/visibility');
234
            $visibilities = array();
235 1
            foreach ($visibility as $item) {
236
                $visibilities = $visibilities + explode(',', $item);
237
            }
238 1
            $visibility = null;
239
            foreach ($visibilities as $item) {
240
                switch ($item) {
241 1
                    case 'public':
242
                        $visibility |= ProjectDescriptor\Settings::VISIBILITY_PUBLIC;
243 1
                        break;
244
                    case 'protected':
245
                        $visibility |= ProjectDescriptor\Settings::VISIBILITY_PROTECTED;
246 1
                        break;
247 1
                    case 'private':
248
                        $visibility |= ProjectDescriptor\Settings::VISIBILITY_PRIVATE;
249 1
                        break;
250
                }
251
            }
252 1
            if ($visibility === null) {
253 1
                $visibility = ProjectDescriptor\Settings::VISIBILITY_DEFAULT;
254
            }
255 1
            if ($input->getOption('parseprivate')) {
256
                $visibility |= ProjectDescriptor\Settings::VISIBILITY_INTERNAL;
257 1
            }
258
            $projectDescriptor->getSettings()->setVisibility($visibility);
259
            $input->getOption('sourcecode')
260
                ? $projectDescriptor->getSettings()->includeSource()
261
                : $projectDescriptor->getSettings()->excludeSource();
262
263
            $this->getParser()->parse($builder, $files);
264 1
        } catch (FilesNotFoundException $e) {
265
            throw new \Exception($this->__('PPCPP:EXC-NOFILES'));
266
        } catch (\Exception $e) {
267
            throw new \Exception($e->getMessage(), 0, $e);
268 1
        }
269
270 1
        if ($progress) {
271 1
            $progress->finish();
272 1
        }
273
274 1
        $projectDescriptor->setPartials($this->partials);
275
276 1
        $output->write($this->__('PPCPP:LOG-STORECACHE', (array)$this->getCache()->getOptions()->getCacheDir()));
277
        $projectDescriptor->getSettings()->clearModifiedFlag();
278
        $mapper->save($projectDescriptor);
279
280
        $output->writeln($this->__('PPCPP:LOG-OK'));
281
282
        return 0;
283 1
    }
284
285
    /**
286 1
     * Returns the collection of files based on the input and configuration.
287
     *
288 1
     * @param InputInterface $input
289 1
     *
290 1
     * @return File[]
291 1
     * @throws \InvalidArgumentException
292 1
     */
293 1
    protected function getFileCollection($input): array
294 1
    {
295 1
        /** @var ConfigurationHelper $configurationHelper */
296
        $configurationHelper = $this->getHelper('phpdocumentor_configuration');
297 1
298
        $ignoreHidden = $configurationHelper->getOption($input, 'hidden', 'files/ignore-hidden', 'off');
299
        $file_options = (array)$configurationHelper->getOption($input, 'filename', 'files/files', array(), true);
300
        $directory_options = $configurationHelper->getOption($input, 'directory', 'files/directories', array(), true);
301
302
303
        $ignorePaths = array_map(
304
            function($value) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after FUNCTION keyword; 0 found
Loading history...
305
                if (substr($value, -1) === '*') {
306 1
                    return substr($value, 0, -1);
307
                }
308
309 1
                return $value;
310
            },
311 1
            $configurationHelper->getOption($input, 'ignore', 'files/ignore', array(), true)
312 1
        );
313 1
314 1
        $fileSpecificationFactory = new SpecificationFactory();
315 1
        $fileSpec = $fileSpecificationFactory->create(
316 1
            array_merge($file_options, $directory_options),
317 1
            [
318
                'paths' => $ignorePaths,
319
                'hidden' => $ignoreHidden !== 'off' && $ignoreHidden === false
320 1
            ],
321 1
            $configurationHelper->getOption(
0 ignored issues
show
Bug introduced by
It seems like $configurationHelper->ge...'php3', 'phtml'), true) targeting phpDocumentor\Command\He...tionHelper::getOption() can also be of type string; however, phpDocumentor\Infrastruc...cationFactory::create() does only seem to accept array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
322 1
                $input,
323 1
                'extensions',
324 1
                'parser/extensions',
325
                array('php', 'php3', 'phtml'),
326
                true
327 1
            )
328 1
        );
329 1
330
        //TODO: Fix this, should we support symlinks? Or just print an error here.
331
        if ($configurationHelper->getOption($input, 'ignore-symlinks', 'files/ignore-symlinks', 'off') == 'on') {
332
            echo "Symlinks are not supported";
333
        }
334
335
        $flySystemFactory = new FlySystemFactory(new MountManager([]));
336
337
        $fileSystems = [];
338
339
        foreach ($directory_options as $option) {
0 ignored issues
show
Bug introduced by
The expression $directory_options of type string|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
340
            $fileSystems[] = $flySystemFactory->create(new Dsn('file://' . realpath($option)));
341
        }
342
343
        $files = [];
344
        foreach ($fileSystems as $fileSystem) {
345
            $result = $fileSystem->find($fileSpec);
346 1
            foreach ($result as $file) {
347
                $files[] = new FlySystemFile($fileSystem, $file['path']);
348 1
            }
349 1
        }
350 1
351
        return $files;
352
    }
353
354
    /**
355
     * Adds the parser.file.pre event to the advance the progressbar.
356
     *
357
     * @param InputInterface $input
358
     *
359
     * @return \Symfony\Component\Console\Helper\HelperInterface|null
360
     */
361
    protected function getProgressBar(InputInterface $input)
362
    {
363
        $progress = parent::getProgressBar($input);
364
        if (!$progress) {
365
            return null;
366
        }
367 1
368
        $this->getService('event_dispatcher')->addListener(
369 1
            'parser.file.pre',
370
            function (PreFileEvent $event) use ($progress) {
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...
371
                $progress->advance();
372
            }
373
        );
374
375
        return $progress;
376
    }
377
378
    /**
379 1
     * Translates the provided text and replaces any contained parameters using printf notation.
380
     *
381 1
     * @param string $text
382 1
     * @param string[] $parameters
383 1
     *
384
     * @return string
385
     */
386
    // @codingStandardsIgnoreStart
387
    protected function __($text, $parameters = array())
388
        // @codingStandardsIgnoreEnd
389
    {
0 ignored issues
show
Coding Style introduced by
Opening brace should be on the line after the declaration; found 1 blank line(s)
Loading history...
390
        return vsprintf($this->translator->translate($text), $parameters);
391
    }
392
393
    /**
394
     * @param InputInterface $input
395
     */
396
    protected function populateParser(InputInterface $input)
397
    {
398
        /** @var ConfigurationHelper $configurationHelper */
399
        $configurationHelper = $this->getHelper('phpdocumentor_configuration');
400
401
        $title = (string)$configurationHelper->getOption($input, 'title', 'title');
402
        $this->getBuilder()->getProjectDescriptor()->setName($title ?: 'API Documentation');
403
        $parserPopulator = new ParserPopulator();
404
        $parserPopulator->populate(
405 1
            $this->getParser(),
406
            $input,
407
            $configurationHelper
408 1
        );
409
    }
410
}
411