Completed
Push — develop ( 722f70...af048b )
by Jaap
15:12 queued 05:04
created

ParseCommand::execute()   F

Complexity

Conditions 18
Paths 3784

Size

Total Lines 106
Code Lines 72

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 18
eloc 72
nc 3784
nop 2
dl 0
loc 106
rs 2
c 0
b 0
f 0

How to fix   Long Method    Complexity   

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 phpDocumentor\Command\Command;
15
use phpDocumentor\Command\Helper\ConfigurationHelper;
16
use phpDocumentor\Descriptor\Cache\ProjectDescriptorMapper;
17
use phpDocumentor\Descriptor\Example\Finder;
18
use phpDocumentor\Descriptor\ProjectDescriptor;
19
use phpDocumentor\Descriptor\ProjectDescriptorBuilder;
20
use phpDocumentor\Fileset\Collection;
21
use phpDocumentor\Parser\Event\PreFileEvent;
22
use phpDocumentor\Parser\Exception\FilesNotFoundException;
23
use phpDocumentor\Parser\Parser;
24
use phpDocumentor\Parser\Util\ParserPopulator;
25
use Symfony\Component\Console\Helper\ProgressHelper;
26
use Symfony\Component\Console\Input\InputInterface;
27
use Symfony\Component\Console\Input\InputOption;
28
use Symfony\Component\Console\Output\OutputInterface;
29
use Symfony\Component\Filesystem\Filesystem;
30
use Zend\Cache\Storage\StorageInterface;
31
use Zend\I18n\Translator\Translator;
32
33
/**
34
 * Parses the given source code and creates a structure file.
35
 *
36
 * The parse task uses the source files defined either by -f or -d options and
37
 * generates a structure file (structure.xml) at the target location (which is
38
 * the folder 'output' unless the option -t is provided).
39
 */
40
class ParseCommand extends Command
0 ignored issues
show
Complexity introduced by
The class ParseCommand has a coupling between objects value of 18. Consider to reduce the number of dependencies under 13.
Loading history...
41
{
42
    /** @var Collection */
43
    private $files;
44
45
    /** @var ProjectDescriptorBuilder $builder*/
46
    protected $builder;
47
48
    /** @var Parser $parser */
49
    protected $parser;
50
51
    /** @var Translator */
52
    protected $translator;
53
54
    public function __construct($builder, $parser, $translator, $files)
55
    {
56
        $this->builder    = $builder;
57
        $this->parser     = $parser;
58
        $this->translator = $translator;
59
        $this->files      = $files;
60
61
        parent::__construct('project:parse');
62
    }
63
64
    /**
65
     * @return ProjectDescriptorBuilder
66
     */
67
    public function getBuilder()
68
    {
69
        return $this->builder;
70
    }
71
72
    /**
73
     * @return \phpDocumentor\Parser\Parser
74
     */
75
    public function getParser()
76
    {
77
        return $this->parser;
78
    }
79
80
    /**
81
     * Returns the Cache.
82
     *
83
     * @return StorageInterface
84
     */
85
    protected function getCache()
86
    {
87
        return $this->getContainer()->offsetGet('descriptor.cache');
88
    }
89
90
    /**
91
     * Initializes this command and sets the name, description, options and
92
     * arguments.
93
     *
94
     * @return void
95
     */
96
    protected function configure()
97
    {
98
        // minimization of the following expression
99
        $VALUE_OPTIONAL_ARRAY = InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY;
100
101
        $this->setAliases(array('parse'))
102
            ->setDescription($this->__('PPCPP-DESCRIPTION'))
103
            ->setHelp($this->__('PPCPP-HELPTEXT'))
104
            ->addOption('filename', 'f', $VALUE_OPTIONAL_ARRAY, $this->__('PPCPP:OPT-FILENAME'))
105
            ->addOption('directory', 'd', $VALUE_OPTIONAL_ARRAY, $this->__('PPCPP:OPT-DIRECTORY'))
106
            ->addOption('target', 't', InputOption::VALUE_OPTIONAL, $this->__('PPCPP:OPT-TARGET'))
107
            ->addOption('encoding', null, InputOption::VALUE_OPTIONAL, $this->__('PPCPP:OPT-ENCODING'))
108
            ->addOption('extensions', 'e', $VALUE_OPTIONAL_ARRAY, $this->__('PPCPP:OPT-EXTENSIONS'))
109
            ->addOption('ignore', 'i', $VALUE_OPTIONAL_ARRAY, $this->__('PPCPP:OPT-IGNORE'))
110
            ->addOption('ignore-tags', null, $VALUE_OPTIONAL_ARRAY, $this->__('PPCPP:OPT-IGNORETAGS'))
111
            ->addOption('hidden', null, InputOption::VALUE_NONE, $this->__('PPCPP:OPT-HIDDEN'))
112
            ->addOption('ignore-symlinks', null, InputOption::VALUE_NONE, $this->__('PPCPP:OPT-IGNORESYMLINKS'))
113
            ->addOption('markers', 'm', $VALUE_OPTIONAL_ARRAY, $this->__('PPCPP:OPT-MARKERS'), array('TODO', 'FIXME'))
114
            ->addOption('title', null, InputOption::VALUE_OPTIONAL, $this->__('PPCPP:OPT-TITLE'))
115
            ->addOption('force', null, InputOption::VALUE_NONE, $this->__('PPCPP:OPT-FORCE'))
116
            ->addOption('validate', null, InputOption::VALUE_NONE, $this->__('PPCPP:OPT-VALIDATE'))
117
            ->addOption('visibility', null, $VALUE_OPTIONAL_ARRAY, $this->__('PPCPP:OPT-VISIBILITY'))
118
            ->addOption('sourcecode', null, InputOption::VALUE_NONE, $this->__('PPCPP:OPT-SOURCECODE'))
119
            ->addOption('progressbar', 'p', InputOption::VALUE_NONE, $this->__('PPCPP:OPT-PROGRESSBAR'))
120
            ->addOption('parseprivate', null, InputOption::VALUE_NONE, 'PPCPP:OPT-PARSEPRIVATE')
121
            ->addOption(
122
                'defaultpackagename',
123
                null,
124
                InputOption::VALUE_OPTIONAL,
125
                $this->__('PPCPP:OPT-DEFAULTPACKAGENAME'),
126
                'Default'
127
            );
128
129
        parent::configure();
130
    }
131
132
    /**
133
     * Executes the business logic involved with this command.
134
     *
135
     * @param InputInterface  $input
136
     * @param OutputInterface $output
137
     *
138
     * @throws \Exception if the target location is not a folder.
139
     *
140
     * @return integer
141
     */
142
    protected function execute(InputInterface $input, OutputInterface $output)
0 ignored issues
show
Complexity introduced by
This operation has 20736 execution paths which exceeds the configured maximum of 200.

A high number of execution paths generally suggests many nested conditional statements and make the code less readible. This can usually be fixed by splitting the method into several smaller methods.

You can also find more information in the “Code” section of your repository.

Loading history...
143
    {
144
        /** @var ConfigurationHelper $configurationHelper */
145
        $configurationHelper = $this->getHelper('phpdocumentor_configuration');
146
        $target = $configurationHelper->getOption($input, 'target', 'parser/target');
147
        if (strpos($target, '/tmp/') === 0) {
148
            $target = str_replace('/tmp/', sys_get_temp_dir() . DIRECTORY_SEPARATOR, $target);
149
        }
150
151
        $fileSystem = new Filesystem();
152
        if (! $fileSystem->isAbsolutePath($target)) {
153
            $target = getcwd().DIRECTORY_SEPARATOR.$target;
154
        }
155
        if (!file_exists($target)) {
156
            mkdir($target, 0777, true);
157
        }
158
        if (!is_dir($target)) {
159
            throw new \Exception($this->__('PPCPP:EXC-BADTARGET'));
160
        }
161
        $this->getCache()->getOptions()->setCacheDir($target);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Zend\Cache\Storage\Adapter\AdapterOptions as the method setCacheDir() does only exist in the following sub-classes of Zend\Cache\Storage\Adapter\AdapterOptions: Zend\Cache\Storage\Adapter\FilesystemOptions. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

class MyUser extends 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 sub-classes 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 parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
162
163
        $builder = $this->getBuilder();
164
        $builder->createProjectDescriptor();
165
        $projectDescriptor = $builder->getProjectDescriptor();
166
167
        $output->write($this->__('PPCPP:LOG-COLLECTING'));
168
        $files = $this->getFileCollection($input);
169
170
        /** @var Finder $exampleFinder */
171
        $exampleFinder = $this->getContainer()->offsetGet('parser.example.finder');
172
        $exampleFinder->setSourceDirectory($files->getProjectRoot());
173
        $exampleFinder->setExampleDirectories($configurationHelper->getConfigValueFromPath('files/examples'));
174
        $output->writeln($this->__('PPCPP:LOG-OK'));
175
176
        /** @var ProgressHelper $progress  */
177
        $progress = $this->getProgressBar($input);
178
        if (!$progress) {
179
            $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...
180
        }
181
182
        $output->write($this->__('PPCPP:LOG-INITIALIZING'));
183
        $this->populateParser($input, $files);
184
185
        if ($progress) {
186
            $progress->start($output, $files->count());
187
        }
188
189
        try {
190
            $output->writeln($this->__('PPCPP:LOG-OK'));
191
            $output->writeln($this->__('PPCPP:LOG-PARSING'));
192
193
            $mapper = new ProjectDescriptorMapper($this->getCache());
194
            $mapper->garbageCollect($files);
195
            $mapper->populate($projectDescriptor);
196
197
            $visibility = (array) $configurationHelper->getOption($input, 'visibility', 'parser/visibility');
198
            $visibilities = array();
199
            foreach ($visibility as $item) {
200
                $visibilities = $visibilities + explode(',', $item);
201
            }
202
            $visibility = null;
203
            foreach ($visibilities as $item) {
204
                switch ($item) {
205
                    case 'public':
206
                        $visibility |= ProjectDescriptor\Settings::VISIBILITY_PUBLIC;
207
                        break;
208
                    case 'protected':
209
                        $visibility |= ProjectDescriptor\Settings::VISIBILITY_PROTECTED;
210
                        break;
211
                    case 'private':
212
                        $visibility |= ProjectDescriptor\Settings::VISIBILITY_PRIVATE;
213
                        break;
214
                }
215
            }
216
            if ($visibility === null) {
217
                $visibility = ProjectDescriptor\Settings::VISIBILITY_DEFAULT;
218
            }
219
            if ($input->getOption('parseprivate')) {
220
                $visibility = $visibility | ProjectDescriptor\Settings::VISIBILITY_INTERNAL;
221
            }
222
            $projectDescriptor->getSettings()->setVisibility($visibility);
223
            $input->getOption('sourcecode')
224
                ? $projectDescriptor->getSettings()->includeSource()
225
                : $projectDescriptor->getSettings()->excludeSource();
226
227
            $this->getParser()->parse($builder, $files);
228
        } catch (FilesNotFoundException $e) {
229
            throw new \Exception($this->__('PPCPP:EXC-NOFILES'));
230
        } catch (\Exception $e) {
231
            throw new \Exception($e->getMessage(), 0, $e);
232
        }
233
234
        if ($progress) {
235
            $progress->finish();
236
        }
237
238
        $projectDescriptor->setPartials($this->getService('partials'));
0 ignored issues
show
Documentation introduced by
$this->getService('partials') is of type object<stdClass>|null, but the function expects a object<phpDocumentor\Descriptor\Collection>.

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...
239
240
        $output->write($this->__('PPCPP:LOG-STORECACHE', (array) $this->getCache()->getOptions()->getCacheDir()));
241
        $projectDescriptor->getSettings()->clearModifiedFlag();
242
        $mapper->save($projectDescriptor);
243
244
        $output->writeln($this->__('PPCPP:LOG-OK'));
245
246
        return 0;
247
    }
248
249
    /**
250
     * @param InputInterface $input
251
     * @param Collection     $files
252
     */
253
    protected function populateParser(InputInterface $input, Collection $files)
254
    {
255
        /** @var ConfigurationHelper $configurationHelper */
256
        $configurationHelper = $this->getHelper('phpdocumentor_configuration');
257
258
        $title = (string) $configurationHelper->getOption($input, 'title', 'title');
259
        $this->getBuilder()->getProjectDescriptor()->setName($title ?: 'API Documentation');
260
        $parserPopulator = new ParserPopulator();
261
        $parserPopulator->populate(
262
            $this->getParser(),
263
            $input,
264
            $configurationHelper,
265
            $files
266
        );
267
    }
268
269
    /**
270
     * Returns the collection of files based on the input and configuration.
271
     *
272
     * @param InputInterface $input
273
     *
274
     * @return Collection
275
     */
276
    protected function getFileCollection($input)
277
    {
278
        /** @var ConfigurationHelper $configurationHelper */
279
        $configurationHelper = $this->getHelper('phpdocumentor_configuration');
280
281
        $this->files->setAllowedExtensions(
282
            $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\Fileset\Co...:setAllowedExtensions() 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...
283
                $input,
284
                'extensions',
285
                'parser/extensions',
286
                array('php', 'php3', 'phtml'),
287
                true
288
            )
289
        );
290
        $this->files->setIgnorePatterns($configurationHelper->getOption($input, 'ignore', 'files/ignore', array(), true));
0 ignored issues
show
Bug introduced by
It seems like $configurationHelper->ge...ignore', array(), true) targeting phpDocumentor\Command\He...tionHelper::getOption() can also be of type string; however, phpDocumentor\Fileset\Co...on::setIgnorePatterns() 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...
291
        $ignoreHidden = $configurationHelper->getOption($input, 'hidden', 'files/ignore-hidden', 'off');
292
        $this->files->setIgnoreHidden($ignoreHidden !== 'off' && $ignoreHidden === false);
293
        $this->files->setFollowSymlinks(
294
            $configurationHelper->getOption($input, 'ignore-symlinks', 'files/ignore-symlinks', 'off') == 'on'
295
        );
296
297
        $file_options = (array) $configurationHelper->getOption($input, 'filename', 'files/files', array(), true);
298
        $added_files = array();
299
        foreach ($file_options as $glob) {
300
            if (!is_string($glob)) {
301
                continue;
302
            }
303
304
            $matches = glob($glob);
305
            if (is_array($matches)) {
306
                foreach ($matches as $file) {
307
                    if (!empty($file)) {
308
                        $file = realpath($file);
309
                        if (!empty($file)) {
310
                            $added_files[] = $file;
311
                        }
312
                    }
313
                }
314
            }
315
        }
316
        $this->files->addFiles($added_files);
317
318
        $directory_options = $configurationHelper->getOption($input, 'directory', 'files/directories', array(), true);
319
        $added_directories = array();
320
        foreach ($directory_options as $glob) {
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...
321
            if (!is_string($glob)) {
322
                continue;
323
            }
324
325
            $matches = glob($glob, GLOB_ONLYDIR);
326
            if (is_array($matches)) {
327
                foreach ($matches as $dir) {
328
                    if (!empty($dir)) {
329
                        $dir = realpath($dir);
330
                        if (!empty($dir)) {
331
                            $added_directories[] = $dir;
332
                        }
333
                    }
334
                }
335
            }
336
        }
337
        $this->files->addDirectories($added_directories);
338
339
        return $this->files;
340
    }
341
342
    /**
343
     * Adds the parser.file.pre event to the advance the progressbar.
344
     *
345
     * @param InputInterface $input
346
     *
347
     * @return \Symfony\Component\Console\Helper\HelperInterface|null
348
     */
349
    protected function getProgressBar(InputInterface $input)
350
    {
351
        $progress = parent::getProgressBar($input);
352
        if (!$progress) {
353
            return null;
354
        }
355
356
        $this->getService('event_dispatcher')->addListener(
357
            'parser.file.pre',
358
            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...
359
                $progress->advance();
360
            }
361
        );
362
363
        return $progress;
364
    }
365
366
    /**
367
     * Translates the provided text and replaces any contained parameters using printf notation.
368
     *
369
     * @param string   $text
370
     * @param string[] $parameters
371
     *
372
     * @return string
373
     */
374
    // @codingStandardsIgnoreStart
375
    protected function __($text, $parameters = array())
0 ignored issues
show
Coding Style introduced by
This method's name is shorter than the configured minimum length of 3 characters.

Even though PHP does not care about the name of your methods, it is generally a good practice to choose method names which can be easily understood by other human readers.

Loading history...
376
    // @codingStandardsIgnoreEnd
377
    {
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...
378
        return vsprintf($this->translator->translate($text), $parameters);
379
    }
380
}
381